/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
module dwt.custom.styledtexthelper;


private import dwt.dwt;

private import dwt.custom.styledtext;
private import dwt.events.events;
private import dwt.events.listeners;
private import dwt.events.typedevent;
private import dwt.graphics.color;
private import dwt.graphics.device;
private import dwt.graphics.font;
private import dwt.graphics.fontdata;
private import dwt.graphics.fontmetrics;
private import dwt.graphics.gc;
private import dwt.graphics.point;
private import dwt.graphics.rectangle;
private import dwt.graphics.textlayout;
private import dwt.graphics.textstyle;
private import dwt.internal.compatibility;
private import dwt.internal.dwteventlistener;
private import dwt.internal.win32.os;
private import dwt.util.hashtable;
private import dwt.util.javatypes;
private import dwt.util.util;
private import dwt.util.vector;
private import dwt.widgets.event;
private import dwt.widgets.typedlistener;


version(DRAG_DROP){

private import dwt.dnd.bytearraytransfer;
private import dwt.dnd.clipboard;
private import dwt.dnd.dnd;
private import dwt.dnd.dndevent;
private import dwt.dnd.dndlistener;
private import dwt.dnd.dragsource;
private import dwt.dnd.dragsourceadapter;
private import dwt.dnd.dragsourceevent;
private import dwt.dnd.dragsourcelistener;
private import dwt.dnd.dragundereffect;
private import dwt.dnd.droptarget;
private import dwt.dnd.droptargetadapter;
private import dwt.dnd.droptargetevent;
private import dwt.dnd.droptargetlistener;
private import dwt.dnd.filetransfer;
private import dwt.dnd.nodragundereffect;
private import dwt.dnd.oleenumformatetc;
private import dwt.dnd.rtftransfer;
private import dwt.dnd.tabledragundereffect;
private import dwt.dnd.texttransfer;
private import dwt.dnd.transfer;
private import dwt.dnd.transferdata;
private import dwt.dnd.treedragundereffect;

}




// there is forward reference issue about StyledText, 
// so store all helper classes/interfaces in one file here


/**
 * This class provides access to the public constants provided by <code>StyledText</code>.
 */
public class ST {
	
	/* StyledText key action constants.  Key bindings for the actions are set
	 * by the StyledText widget.  @see StyledText#createKeyBindings()
	 */
	 
	/* Navigation Key Actions */	
	public static const int LINE_UP = 16777217; 			// binding = DWT.ARROW_UP
	public static const int LINE_DOWN = 16777218; 		// binding = DWT.ARROW_DOWN
	public static const int LINE_START = 16777223; 		// binding = DWT.HOME
	public static const int LINE_END = 16777224; 		// binding = DWT.END
	public static const int COLUMN_PREVIOUS = 16777219; 	// binding = DWT.ARROW_LEFT
	public static const int COLUMN_NEXT = 16777220; 		// binding = DWT.ARROW_RIGHT
	public static const int PAGE_UP = 16777221; 			// binding = DWT.PAGE_UP
	public static const int PAGE_DOWN = 16777222; 		// binding = DWT.PAGE_DOWN
	public static const int WORD_PREVIOUS = 17039363;	// binding = DWT.MOD1 + DWT.ARROW_LEFT
	public static const int WORD_NEXT = 17039364; 		// binding = DWT.MOD1 + DWT.ARROW_RIGHT
	public static const int TEXT_START = 17039367; 		// binding = DWT.MOD1 + DWT.HOME
	public static const int TEXT_END = 17039368; 		// binding = DWT.MOD1 + DWT.END
	public static const int WINDOW_START = 17039365; 	// binding = DWT.MOD1 + DWT.PAGE_UP
	public static const int WINDOW_END = 17039366; 		// binding = DWT.MOD1 + DWT.PAGE_DOWN

	/* Selection Key Actions */
	public static const int SELECT_LINE_UP = 16908289; 			// binding = DWT.MOD2 + DWT.ARROW_UP
	public static const int SELECT_LINE_DOWN = 16908290; 		// binding = DWT.MOD2 + DWT.ARROW_DOWN
	public static const int SELECT_LINE_START = 16908295; 		// binding = DWT.MOD2 + DWT.HOME
	public static const int SELECT_LINE_END = 16908296; 			// binding = DWT.MOD2 + DWT.END
	public static const int SELECT_COLUMN_PREVIOUS = 16908291;	// binding = DWT.MOD2 + DWT.ARROW_LEFT
	public static const int SELECT_COLUMN_NEXT = 16908292; 		// binding = DWT.MOD2 + DWT.ARROW_RIGHT
	public static const int SELECT_PAGE_UP = 16908293; 			// binding = DWT.MOD2 + DWT.PAGE_UP
	public static const int SELECT_PAGE_DOWN = 16908294; 		// binding = DWT.MOD2 + DWT.PAGE_DOWN
	public static const int SELECT_WORD_PREVIOUS = 17170435;		// binding = DWT.MOD1 + DWT.MOD2 + DWT.ARROW_LEFT
	public static const int SELECT_WORD_NEXT = 17170436; 		// binding = DWT.MOD1 + DWT.MOD2 + DWT.ARROW_RIGHT
	public static const int SELECT_TEXT_START = 17170439; 		// binding = DWT.MOD1 + DWT.MOD2 + DWT.HOME
	public static const int SELECT_TEXT_END = 17170440; 			// binding = DWT.MOD1 + DWT.MOD2 + DWT.END
	public static const int SELECT_WINDOW_START = 17170437; 		// binding = DWT.MOD1 + DWT.MOD2 + DWT.PAGE_UP
	public static const int SELECT_WINDOW_END = 17170438; 		// binding = DWT.MOD1 + DWT.MOD2 + DWT.PAGE_DOWN

	/* Modification Key Actions */
	public static const int CUT = 131199; 			// binding = DWT.MOD2 + DWT.DEL
	public static const int COPY = 17039369; 		// binding = DWT.MOD1 + DWT.INSERT;
	public static const int PASTE = 16908297;		// binding = DWT.MOD2 + DWT.INSERT ;
	public static const int DELETE_PREVIOUS = '\b'; 	// binding = DWT.BS;
	public static const int DELETE_NEXT = 0x7F; 		// binding = DWT.DEL;
	public static const int DELETE_WORD_PREVIOUS = 262152;	// binding = DWT.BS | DWT.MOD1;
	public static const int DELETE_WORD_NEXT = 262271;	// binding = DWT.DEL | DWT.MOD1;

	/* Miscellaneous Key Actions */
	public static const int TOGGLE_OVERWRITE = 16777225; // binding = DWT.INSERT;
}


struct RANGE{
	int Position;
	int Length;		
	void opCall(RANGE r){		
		this.Position = r.Position;
		this.Length = r.Length;
	}
    int opIndex(int i1) {assert(i1<2); return (i1==0 ? Position : Length); };
    int opIndexAssign(int value, int i1) {
    	assert(i1<2); 
    	if(i1==0)
    		Position = value;
    	else
    		Length = value;
    	return value; 
    };
}

/**
 * Clients may implement the StyledTextContent interface to provide a 
 * custom store for the StyledText widget content. The StyledText widget 
 * interacts with its StyledTextContent in order to access and update 
 * the text that is being displayed and edited in the widget. 
 * A custom content implementation can be set in the widget using the
 * StyledText.setContent API.
 */
public interface StyledTextContent {

/**
 * Called by StyledText to add itself as an Observer to content changes.
 * See TextChangeListener for a description of the listener methods that
 * are called when text changes occur.
 * <p>
 *
 * @param listener the listener
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
 * </ul>
 */
public void addTextChangeListener(TextChangeListener listener);

/**
 * Return the number of characters in the content.
 * <p>
 *
 * @return the number of characters in the content.
 */
public int getCharCount();

/**
 * Return the line at the given line index without delimiters.
 * <p>
 *
 * @param lineIndex index of the line to return. Does not include 
 *	delimiters of preceeding lines. Index 0 is the first line of the 
 * 	content.
 * @return the line text without delimiters
 */
public char[] getLine(int lineIndex);

/**
 * Return the line index at the given character offset.
 * <p>
 *
 * @param offset offset of the line to return. The first character of the 
 * 	document is at offset 0.  An offset of getLength() is valid and should 
 *	answer the number of lines. 
 * @return the line index. The first line is at index 0.  If the character 
 * 	at offset is a delimiter character, answer the line index of the line 
 * 	that is delimited. 
 * 	For example, if text = "\r\n\r\n", and delimiter = "\r\n", then:
 * <ul>
 * <li>getLineAtOffset(0) == 0
 * <li>getLineAtOffset(1) == 0
 * <li>getLineAtOffset(2) == 1
 * <li>getLineAtOffset(3) == 1
 * <li>getLineAtOffset(4) == 2
 * </ul>
 */
public int getLineAtOffset(int offset);

/**
 * Return the number of lines.  Should answer 1 when no text is specified.
 * The  StyledText widget relies on this behavior for drawing the cursor.
 * <p>
 *
 * @return the number of lines.  For example:
 * <ul>
 * <li>	text value ==> getLineCount		
 * <li>	null ==> 1		
 * <li>	"" ==> 1		
 * <li>	"a\n" ==> 2			
 * <li>	"\n\n" ==> 3			
 * </ul>
 */
public int getLineCount();

/**
 * Return the line delimiter that should be used by the StyledText 
 * widget when inserting new lines. New lines entered using key strokes 
 * and paste operations use this line delimiter.
 * Implementors may use System.getProperty("line.separator") to return
 * the platform line delimiter.
 * <p>
 *
 * @return the line delimiter that should be used by the StyledText widget
 *	when inserting new lines.
 */
public char[] getLineDelimiter();

/**
 * Return the character offset of the first character of the given line.
 * <p>
 * <b>NOTE:</b> When there is no text (i.e., no lines), getOffsetAtLine(0) 
 * is a valid call that should return 0.
 * </p>
 *
 * @param lineIndex index of the line. The first line is at index 0.
 * @return offset offset of the first character of the line. The first 
 * 	character of the document is at offset 0.  The return value should 
 * 	include line delimiters.  
 * 	For example, if text = "\r\ntest\r\n" and delimiter = "\r\n", then:
 * <ul>
 * <li>getOffsetAtLine(0) == 0
 * <li>getOffsetAtLine(1) == 2
 * <li>getOffsetAtLine(2) == 8
 * </ul>
 */
public int getOffsetAtLine(int lineIndex);

/**
 * Returns a string representing the content at the given range.
 * <p>
 *
 * @param start the start offset of the text to return. Offset 0 is the 
 * 	first character of the document.
 * @param length the length of the text to return
 * @return the text at the given range
 */
public char[] getTextRange(int start, int length);

/**
 * Remove the specified text changed listener.
 * <p>
 *
 * @param listener the listener
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
 * </ul>
 */
public void removeTextChangeListener(TextChangeListener listener);

/**
 * Replace the text with "newText" starting at position "start" 
 * for a length of "replaceLength".
 * <p>
 * Implementors have to notify the TextChangeListeners that were added 
 * using <code>addTextChangeListener</code> before and after the content 
 * is changed. A <code>TextChangingEvent</code> has to be sent to the 
 * textChanging method before the content is changed and a 
 * <code>TextChangedEvent</code> has to be sent to the textChanged method
 * after the content has changed.
 * The text change that occurs after the <code>TextChangingEvent</code> 
 * has been sent has to be consistent with the data provided in the 
 * <code>TextChangingEvent</code>.
 * This data will be cached by the widget and will be used when the 
 * <code>TextChangedEvent</code> is received.
 * <p>
 * The <code>TextChangingEvent</code> should be set as follows:
 * <ul>
 * <li>event.start = start of the replaced text
 * <li>event.newText = text that is going to be inserted or empty String 
 *	if no text will be inserted
 * <li>event.replaceCharCount = length of text that is going to be replaced
 * <li>event.newCharCount = length of text that is going to be inserted
 * <li>event.replaceLineCount = number of lines that are going to be replaced
 * <li>event.newLineCount = number of new lines that are going to be inserted
 * </ul>
 * <b>NOTE:</b> newLineCount is the number of inserted lines and replaceLineCount 
 * is the number of deleted lines based on the change that occurs visually.  
 * For example:
 * <ul>
 * <li>(replaceText, newText) ==> (replaceLineCount, newLineCount)
 * <li>("", "\n") ==> (0, 1)
 * <li>("\n\n", "a") ==> (2, 0)
 * <li>("a", "\n\n") ==> (0, 2)
 * <li>("\n", "") ==> (1, 0)
 * </ul>
 * </p>
 *
 * @param start start offset of text to replace, none of the offsets include 
 *	delimiters of preceeding lines, offset 0 is the first character of the 
 * 	document 
 * @param replaceLength start offset of text to replace
 * @param newText start offset of text to replace
 * @see TextChangeListener
 */
public void replaceTextRange(int start, int replaceLength, char[] text);

/**
 * Set text to "text".
 * Implementors have to send a <code>TextChangedEvent</code> to the 
 * textSet method of the TextChangeListeners that were added using 
 * <code>addTextChangeListener</code>.
 * <p>
 *
 * @param text the new text
 * @see TextChangeListener
 */
public void setText(char[] text);

}


/**
 * This event is sent after a text change occurs.
 */
public class ExtendedModifyEvent : TypedEvent {
	public int start;			// start offset of the new text
	public int length;			// length of the new text
	public char[] replacedText;	// replaced text or empty string if no text was replaced
	
public this(StyledTextEvent e) {
	super(cast(Event)e);
	start = e.start;
	length = e.end - e.start;
	replacedText = e.text;
}
}


public interface ExtendedModifyListener : DWTEventListener {
/**
 * This method is called after a text change occurs.
 * <p>
 *
 * @param event.start the start offset of the new text (input)
 * @param event.length the length of the new text (input)
 * @param event.replacedText the replaced text (input)
 */
public void modifyText(ExtendedModifyEvent event);
}

/**
 * This event is sent when a line is about to be drawn.
 */
public class LineBackgroundEvent : TypedEvent {
	public int lineOffset;			// line start offset 
	public char[] lineText;			// line text
	public Color lineBackground;	// line background color
	
public this(StyledTextEvent e) {
	super(cast(Event)e);
	lineOffset = e.detail;
	lineText = e.text;
}
}

public interface LineBackgroundListener : DWTEventListener {
	
/**
 * This method is called when a line is about to be drawn in order to get its
 * background color.
 * <p>
 *
 * @param event.lineOffset line start offset (input)	
 * @param event.lineText line text (input)
 * @param event.lineBackground line background color (output)
 */
public void lineGetBackground(LineBackgroundEvent event);
}



/**
 * This event is sent when a line is about to be drawn.
 */
public class LineStyleEvent : TypedEvent {
	public int lineOffset;			// line start offset 
	public char[] lineText;			// line text
	public StyleRange[] styles;		// array of StyleRanges

public this(StyledTextEvent e) {
	super(cast(Event)e);
	lineOffset = e.detail;
	lineText = e.text;
	styles = e.styles;
}
}


public interface LineStyleListener : DWTEventListener {
/**
 * This method is called when a line is about to be drawn in order to get the
 * line's style information.
 * <p>
 *
 * @param event.lineOffset line start offset (input)	
 * @param event.lineText line text (input)
 * @param event.styles array of StyleRanges, need to be in order (output)
 */
public void lineGetStyle(LineStyleEvent event);
}

public interface VerifyKeyListener : DWTEventListener {
/**
 * @param event.character the character that was typed (input)	
 * @param event.keyCode the key code that was typed (input)
 * @param event.stateMask the state of the keyboard (input)
 * @param event.doit processed or not (output)
 */
public void verifyKey (VerifyEvent event);
}

class DefaultLineStyler : LineStyleListener, LineBackgroundListener {
	StyledTextContent content;
	StyleRange[] styles;
	int styleCount = 0;	// the number of styles	
	int styleExpandExp = 1; // the expansion exponent, used to increase the styles array exponentially
	int lineExpandExp = 1; 	// the expansion exponent, used to increase the lines array exponentially
	int lineCount = 0;
	Color lineBackgrounds[];
	
/** 
 * Creates a new default line styler.
 * <p>
 *
 * @param content the text to which the styles apply
 */
public this(StyledTextContent content) {
	this.content = content;
	lineCount = content.getLineCount();
	lineBackgrounds = new Color[lineCount];
}	
/** 
 * Inserts a style at the given location.
 * <p>
 *
 * @param style	the new style
 * @param index	the index at which to insert the style (the new style
 * 	will reside at this index)
 *
 */
void insertStyle(StyleRange style, int index) {
	StyleRange[] StyleRangeArr;
	StyleRangeArr ~= style;
	insertStyles(StyleRangeArr, index);
}
/** 
 * Insert the styles at the given location.
 * <p>
 *
 * @param insertStyles	the new styles
 * @param index	the index at which to insert the styles (the first new style
 * 	will reside at this index)
 *
 */
void insertStyles(StyleRange[] insertStyles, int index) {
	int size = styles.length;
	int insertCount = insertStyles.length;
	int spaceNeeded = styleCount + insertCount - size;
	if (spaceNeeded > 0) {
		StyleRange[] newStyles = new StyleRange[size+spaceNeeded];
		System.arraycopy(cast(Object[])styles, 0, cast(Object[])newStyles, 0, size);
		styles = newStyles;
	}
	// shift the styles down to make room for the new styles
	System.arraycopy(cast(Object[])styles, index, cast(Object[])styles, index + insertCount, styleCount - index);

	// add the new styles
	System.arraycopy(cast(Object[])insertStyles, 0, cast(Object[])styles, index, insertCount);
	styleCount = styleCount + insertCount;
}
/** 
 * Inserts a style, merging it with adjacent styles if possible.
 * <p>
 *
 * @param style	the new style
 * @param index	the index at which to insert the style (the new style
 * 	will reside at this index)
 * @return true if the style was inserted, false if the style was merged with an adjacent 
 * 	style
 */
boolean insertMergeStyle(StyleRange style, int index) {
	if (mergeStyleBefore(style, index)) return false;
	if (mergeStyleAfter(style, index)) return false;
	insertStyle(style, index);
	return true;
}
/** 
 * Merges the style with the style before it if possible.
 * <p>
 *
 * @param style	the new style
 * @param index	the index at which to attempt the merge.
 * @return true if the style was merged, false otherwise
 */
boolean mergeStyleBefore(StyleRange style, int index) {
	// see if the style is similar to the style before it and merge the
	// styles if possible
	if (index > 0) {
		StyleRange previous = styles[index-1];
		if (style.similarTo(previous)) {
			// the start of style needs to be in the range of the previous style
			// and the end of style needs to be < the start of the next style
			int previousEnd = previous.start + previous.length;
			if ((style.start <= previousEnd) && (style.start >= previous.start)) {
				int styleEnd = style.start + style.length;
				if ((index == styleCount) || (styleEnd <= styles[index].start)) {
					previous.length = style.start + style.length - previous.start;
					return true;
				}
			}
		}
	}
	return false;
}
/** 
 * Merges the style with the style after it if possible.
 * <p>
 *
 * @param style	the new style
 * @param index	the index at which to attempt the merge.
 * @return true if the style was merged, false otherwise
 */
boolean mergeStyleAfter(StyleRange style, int index) {
	// see if the style is similar to the style that will be after it and 
	// merge the styles if possible
	if (index < styleCount) {
		StyleRange next = styles[index];
		if (style.similarTo(next)) {
			// the end of style needs to be in the range of the next style and
			// the start of style needs to be > the end of the previous style
			int styleEnd = style.start + style.length;
			int nextEnd = next.start + next.length;
			if ((styleEnd <= nextEnd) && (styleEnd >= next.start)) {
				if ((index == 0) || (style.start >= styles[index-1].start + styles[index-1].length)) {
					next.length = next.start + next.length - style.start;
					next.start = style.start;
					return true;
				}
			}
		}
	}
	return false;
}
/** 
 * Removes style information that is defined for the range of text in <code>clearStyle</code>.
 * <p>
 *
 * @param clearStyle the style information to use for clearing 
 */ 
void clearStyle(StyleRange clearStyle) {
	Point pt = getOverlappingStyles(clearStyle.start, clearStyle.length);
	int clearStyleEnd = clearStyle.start + clearStyle.length - 1;
	
	// no overlapped styles exist
	if ((pt is null) || (pt.y == 0)) return; 

	// the newStyle overlaps one or more of the existing styles
	// pt.x is the index of the first overlapped style, pt.y is the number of overlapped
	// styles
	int count = 0;
	int deleteStyle = -1;
	int deleteCount = 0;
	for (int i=pt.x; count<pt.y; i++) {
		StyleRange overlap = styles[i];
		int overlapEnd = overlap.start + overlap.length - 1;
		if (overlap.start < clearStyle.start) {
			if (overlapEnd <= clearStyleEnd) {
				// the end of overlap needs to be cleared
				overlap.length=clearStyle.start - overlap.start;
			} else {
				// middle of overlap needs to be cleared, this will
				// cause overlap to be broken into two
				StyleRange endStyle = cast(StyleRange)overlap.clone();
				endStyle.start = clearStyleEnd + 1;
				endStyle.length = overlapEnd - clearStyleEnd;
				overlap.length = clearStyle.start - overlap.start;
				insertStyle(endStyle, i+1);
				break;
			}
		} else {
			if (overlapEnd <= clearStyleEnd) {	
				// entire overlap needs to be cleared
				if (deleteStyle == -1) {
					deleteStyle = i;
				} 
				deleteCount++;
			} else {
				// beginning of overlap needs to be cleared
				overlap.start=clearStyleEnd + 1;
				overlap.length=overlapEnd - overlap.start + 1;
				break;
			}
		}				
		count++;
	}
	deleteStyles(deleteStyle, deleteCount);
}
/**
 * Increases the <code>linebackgrounds</code> array to accomodate new line background
 * information.
 * <p>
 *
 * @param numLines the number to increase the array by
 */
void expandLinesBy(int numLines) {
	int size = lineBackgrounds.length;
	if (size - lineCount >= numLines) {
		return;
	}
	Color[] newLines = new Color[size+Math.max(Compatibility.pow2(lineExpandExp), numLines)];
	System.arraycopy(lineBackgrounds, 0, newLines, 0, size);
	lineBackgrounds = newLines;
	lineExpandExp++;
}
/** 
 * Deletes the style at <code>index</code>.
 * <p>
 *
 * @param index	the index of the style to be deleted
 */
void deleteStyle(int index) {
	deleteStyles(index, 1);
}
/** 
 * Delete count styles starting at <code>index</code>.
 * <p>
 *
 * @param index	the index of the style to be deleted
 * @param count	the number of styles to be deleted
 */
void deleteStyles(int index, int count) {
	if ((count == 0) || (index < 0)) return;
	// shift the styles up 
	System.arraycopy(styles, index + count, styles, index, styleCount - (index + count));
	for (int i=0; i<count; i++) {
		styles[styleCount - i - 1] = null;
	}
	styleCount = styleCount - count;
}
/** 
 * Returns the styles that are defined.
 * <p>
 *
 * @return the copied array of styles
 */
StyleRange [] getStyleRanges() {
	StyleRange[] newStyles = new StyleRange[styleCount];
	System.arraycopy(styles, 0, newStyles, 0, styleCount);
	return newStyles;
}
/**
 * Handles the get line background color callback.
 * <p>
 *
 * @param event.lineOffset line number (input)	
 * @param event.lineText line text (input)
 * @param event.background line background color (output)
 */
public void lineGetBackground(LineBackgroundEvent event) {
	int lineIndex = content.getLineAtOffset(event.lineOffset);
	event.lineBackground = lineBackgrounds[lineIndex];
}
/**
 * Handles the get line style information callback.
 * <p>
 *
 * @param event.lineOffset line number (input)	
 * @param event.lineText line text (input)
 * @param event.styles array of StyleRanges, need to be in order (output)
 */
public void lineGetStyle(LineStyleEvent event) {
	int lineStart = event.lineOffset;
	int lineEnd = lineStart + event.lineText.length;

	int high = searchForStyle(lineStart, lineEnd);
	StyleRange style = null;
	Vector!(StyleRange) lineStyles = new Vector!(StyleRange);

	// index will represent a style that 
	// -- starts after the line (end processing)
	// -- ends before the line (continue processing)
	// -- starts before the line, ends in the line (add range)
	// -- starts in the line, ends in the line (add range)
	// -- starts in the line, ends after the line (add range)
	// -- starts before the line, ends after the line (add range)
	for (int index = high; index < styleCount; index++) {
		style = styles[index];
		if (style.start > lineEnd)
			// style starts after the line, end looping 
			break;
		int styleEnd = style.start + style.length - 1;
		if (styleEnd >= lineStart) lineStyles.addElement(style);
	}
//	event.styles = new StyleRange[lineStyles.getSize()];
//	lineStyles.copyInto(event.styles);
	event.styles = lineStyles.values.dup;
}
/** 
 * Searches for the first style in the <code>start</code> - <code>end</code> range.
 * <p>
 *
 * @return the index of the first style that overlaps the input range  
 */
int searchForStyle(int start, int end) {
	int high = styleCount;
	int low = -1;
	int index = high;
	// find the index of the first style for the given range, use a binary search
	while (high - low > 1) {
		index = (high + low) / 2;
		StyleRange style = styles[index];
		int styleEnd = style.start + style.length - 1;
		if (start <= style.start || end <= styleEnd || (start > style.start && styleEnd >= start && styleEnd < end)) {
			high = index;			
		}
		else {
			low = index;
		}
	}
	return high;
}
/** 
 * Updates the line background colors to reflect a new color.  Called by StyledText.
 * <p>
 *
 * @param startLine index of the first line to color
 * @param lineCount number of lines to color starting at startLine
 * @param background the background color for the lines
 */ 
void setLineBackground(int startLine, int count, Color background) {
	for (int i=startLine; i<startLine + count; i++) {
		lineBackgrounds[i]=background;
	}
}
/** 
 * Update the styles to reflect the new style.  <code>newStyle</code> will 
 * replace any old style for the range.  When this method is called, the 
 * DefaultLineStyler may merge the new style with an existing style (if possible).
 * Called by StyledText when a style is added.  Called by StyledText.
 * <p>
 *
 * @param newStyle the new style information.
 */ 
void setStyleRange(StyleRange newStyle) {
	if (newStyle is null) {
		styleExpandExp = 1;
		styleCount = 0;
		return;
	}
	if (newStyle.length ==0) return;
	if (newStyle.isUnstyled()) {
		clearStyle(newStyle);
		return;
	}
	
	Point pt = getOverlappingStyles(newStyle.start, newStyle.length);
	int newStyleEnd = newStyle.start + newStyle.length - 1;
	
	// no styles exist
	if (pt is null) {
		insertStyle(newStyle, 0);
		return;
	}
	
	// newStyle does not overlap any other styles
	if (pt.y == 0) {
		insertMergeStyle(newStyle, pt.x);
		return;
	} 

	// the newStyle overlaps one or more of the existing styles
	boolean added = false; // indicates whether or not the new style has been added
	int count = 0;
	// pt.x is the index of the first overlapped style, pt.y is the number of overlapped
	// styles
	for (int i=pt.x; count<pt.y; i++) {
		StyleRange overlap = styles[i];
		int overlapEnd = overlap.start + overlap.length - 1;
		if (overlap.start < newStyle.start) {
			if (overlapEnd <= newStyleEnd) {
				// the end of overlap needs to be replaced by newStyle
				if (newStyle.similarTo(overlap)) {
					// update overlap to accomodate the new style
					overlap.length = newStyle.start + newStyle.length - overlap.start;
				} else {
					overlap.length=newStyle.start - overlap.start;
					// see if newStyle can be merged with the style after overlap, if so,
					// processing is done
					if (mergeStyleAfter(newStyle, i+1)) break;
					// otherwise, insert the newStyle, newStyle may still overlap other
					// styles after it so continue processing	
					insertStyle(newStyle, i+1);
					i++;
				}
				added = true;
			} else {
				// middle of overlap needs to be replaced by newStyle, this will
				// cause overlap to be broken into two
				if (newStyle.similarTo(overlap)) break;
				StyleRange endStyle = cast(StyleRange)overlap.clone();
				endStyle.start = newStyleEnd + 1;
				endStyle.length = overlapEnd - newStyleEnd;
				overlap.length = newStyle.start - overlap.start;
				insertStyle(newStyle, i+1);
				i++;
				insertStyle(endStyle, i+1);
				// when newStyle overlaps the middle of a style, this implies that
				// processing is done (no more overlapped styles)
				break;
			}
		} else {
			if (overlapEnd <= newStyleEnd) {	
				// overlap will be replaced by the newStyle, make sure newStyle
				// hasn't already been added, if it has just delete overlap
				if (!added) {
					styles[i] = newStyle;
					added = true;
				} else {
					deleteStyle(i);
					i--;
				}
			} else {
				// beginning of overlap needs to be replaced by newStyle
				overlap.start=newStyleEnd + 1;
				overlap.length=overlapEnd - overlap.start + 1;
				if (!added) {
					insertMergeStyle(newStyle, i);
				}
				// when newStyle overlaps only the beginning of a style, this implies 
				// that processing is done (no more overlapped styles)
				break;
			}
		}				
	count++;
	}
}
/** 
 * Replace the styles for the given range.
 * <p>
 *
 * @param styles the new styles, must be in order and non-overlapping
 */
void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
	clearStyle(new StyleRange(start, length, null, null));
	// find insert point
	int high = styleCount;
	int low = -1;
	int index = high;
	while (high - low > 1) {
		index = (high + low) / 2;
		StyleRange style = styles[index];
		if (start <= style.start) {
			high = index;			
		}
		else {
			low = index;
		}
	}
	insertStyles(ranges, high);
}
/** 
 * Sets the array of styles and discards old styles.  Called by StyledText.
 * <p>
 *
 * @param styles the new styles, must be in order and non-overlapping
 */
void setStyleRanges(StyleRange[] styles) {
	this.styles = new StyleRange[styles.length];
	System.arraycopy(styles, 0, this.styles, 0, styles.length);
	styleCount = styles.length;
	styleExpandExp = 1;
}
/** 
 * Updates the style ranges and line backgrounds to reflect a pending text 
 * change.
 * Called by StyledText when a TextChangingEvent is received.
 * <p>
 *
 * @param event	the event with the text change information
 */  
public void textChanging(TextChangingEvent event) {
	int startLine = content.getLineAtOffset(event.start);
	int startLineOffset = content.getOffsetAtLine(startLine);
	
	textChanging(event.start, -event.replaceCharCount);
	textChanging(event.start, event.newCharCount);
	
	if (event.replaceCharCount == content.getCharCount()) {
		// all text is going to be replaced, clear line backgrounds
		linesChanging(0, -lineCount);
		linesChanging(0, content.getLineCount() - event.replaceLineCount + event.newLineCount);
		return;
	}

	if (event.start != startLineOffset) {
		startLine = startLine + 1;
	}
			
	linesChanging(startLine, -event.replaceLineCount);
	linesChanging(startLine, event.newLineCount);
}
/*
 * Updates the line backgrounds to reflect a pending text change.
 * <p>
 *
 * @param start	the starting line of the change that is about to take place
 * @param delta	the number of lines in the change, > 0 indicates lines inserted,
 * 	< 0 indicates lines deleted
 */
void linesChanging(int start, int delta) {
	if (delta == 0) return;
	boolean inserting = cast(boolean)(delta > 0);
	if (inserting) {
		// shift the lines down to make room for new lines
		expandLinesBy(delta);
		for (int i = lineCount-1; i >= start; i--) {
			lineBackgrounds[i + delta]=lineBackgrounds[i];
		}
		for (int i=start; i<start + delta; i++) {
			lineBackgrounds[i]=null;
		}
	} else {
		// shift up the lines
		for (int i = start - delta; i < lineCount; i++) {
			lineBackgrounds[i+delta]=lineBackgrounds[i];
		}
	}
	lineCount += delta;
}
/*
 * Updates the style ranges to reflect a text change.
 * <p>
 *
 * @param start	the starting offset of the change that is about to 
 *	take place
 * @param delta	the length of the change, > 0 indicates text inserted,
 * 	< 0 indicates text deleted
 */
void textChanging(int start, int delta) {
	if (delta == 0) return;
	StyleRange style;
	// find the index of the first style for the given offset, use a binary search
	// to find the index
	int end;
	int deleteStart = -1;
	int deleteCount = 0;
	boolean inserting = cast(boolean)(delta > 0);
	if (inserting) {
		end = (start + delta) - 1;
	} else {
		end = (start - delta) - 1;
	}
	int high = searchForStyle(start, end);
	int index;
	// update the styles that are in the affected range
	for (index = high; index < styleCount; index++) {
		style = styles[index];
		if (inserting) {
			if (style.start >= start) break;
			// in the insert case only one style range will be directly affected,
			// it will need to be split into two and then the newStyle inserted
			StyleRange beforeStyle = cast(StyleRange)style.clone();
			beforeStyle.length = start - style.start;
			style.start = start;
			style.length = style.length - beforeStyle.length;
			if (beforeStyle.length != 0) insertStyle(beforeStyle, index);
			index++;
			break;
		} else {
			int styleEnd = style.start + style.length - 1;
			if (style.start > end) break;
			// in the delete case, any style that overlaps the change range will be
			// affected
			if (style.start < start) {
				if (styleEnd <= end) {
					// style starts before change range, ends in change range
					style.length = start - style.start;
				} else {
					// style starts before change range, ends after change range
					style.length = style.length + delta;
					index++;
					break;
				}
			} else {
				if (styleEnd <= end) {
					// style starts in change range, ends in change range
					if (deleteStart == -1)	{
						deleteStart = index;
					} 
					deleteCount++;
				} else {
					// style starts in change range, ends after change range
					style.start = start;
					style.length = styleEnd - end;
					index++;
					break;
				}
			}	
		}
	}
	deleteStyles(deleteStart, deleteCount);
	// change the offsets of the styles after the affected styles
	for (int i = index - deleteCount; i < styleCount; i++) {
		style = styles[i];
		style.start = style.start + delta;
	}
}
/** 
 * Returns the indexes of the styles that overlap the given range.  Styles that partially
 * or fully overlap the range will be returned.
 * <p>
 *
 * @return Point where x is the index of the starting overlap style, y is the number of
 * 	styles that overlap the range
 */ 
Point getOverlappingStyles(int start, int length) {	
	StyleRange style;
	if (styleCount == 0) return null;
	// find the index of the first style for the given offset, use a binary search
	// to find the index
	int end = start + length - 1;
	int high = searchForStyle(start, end);
	int count = 0;
	for (int index = high; index < styleCount; index++) {
		style = styles[index];
		int styleEnd = style.start + style.length - 1;
		if (style.start > end) break;
		if (styleEnd >= start) count++;	
	}
	return new Point(high, count);
}
/** 
 * Returns the background color of a line.  Called by StyledText.  It is safe to return 
 * the existing Color object since the colors are set and managed by the client.
 * <p>
 *
 * @param index	the line index
 * @return the background color of the line at the given index
 */ 
Color getLineBackground(int index) {
	return lineBackgrounds[index];
}
/** 
 * Returns the style for the character at <code>offset</code>.  Called by StyledText.  
 * Returns a new style.  Does not return the existing style.
 * <p>
 *
 * @param offset the character position in the text
 * @return a cloned StyleRange with start == offset and length == 1 if a style is
 * 	specified or null if no style is specified
 */ 
StyleRange getStyleRangeAtOffset(int offset) {
	if (styleCount == 0) return null;
	Point pt = getOverlappingStyles(offset, 1);
	if (pt is null || pt.y == 0) return null;
	StyleRange newStyle = cast(StyleRange)styles[pt.x].clone();
	newStyle.start = offset;
	newStyle.length = 1;
	return newStyle;
}
/** 
 * Returns the styles for the given range. Returns the existing styles,
 * so be careful not to modify the return value.  Styles are not cloned
 * in order to make this method as efficient as possible. 
 * <p>
 *
 * @param offset the start position of the text range
 * @param length the length of the text range
 * @return a StyleRange array or null if no styles are specified for the text
 * 	range
 */ 
StyleRange[] getStyleRangesFor(int offset, int length) {
	if (styleCount == 0) return null;
	Point pt = getOverlappingStyles(offset, length);
	if (pt is null || pt.y == 0) return null;
	StyleRange[] ranges = new StyleRange[pt.y];
	for (int i=0; i<pt.y; i++) {
		StyleRange newStyle = styles[pt.x + i];
		ranges[i]=newStyle;
	}
	return ranges;
}
void release () {
	styles = null;
}
}


/**
 * A DisplayRenderer renders the content of a StyledText widget on 
 * a display device.
 */
class DisplayRenderer : StyledTextRenderer {
	private StyledText parent;			// used to get content and styles during rendering
	private int topIndex = -1;
	private TextLayout[] layouts;
	
/**
 * Creates an instance of <class>DisplayRenderer</class>.
 * </p>
 * @param device Device to render on
 * @param regularFont Font to use for regular (non-bold) text
 * @param isBidi true=bidi platform, false=no bidi platform
 * @param leftMargin margin to the left of the text
 * @param parent <class>StyledText</class> widget to render
 * @param tabLength length in characters of a tab character
 */
this(Device device, Font regularFont, StyledText parent, int tabLength) {
	super(device, regularFont);
	this.parent = parent;
	calculateLineHeight();
	setTabLength(tabLength);
}
void dispose() {
	super.dispose();
	if (layouts !is null) {
		for (int i = 0; i < layouts.length; i++) {
			TextLayout layout = layouts[i];
			if (layout !is null) super.disposeTextLayout(layout);
		}
		topIndex = -1;
		layouts = null;
	}
}
/**
 * Dispose the specified GC.
 * </p>
 * @param gc GC to dispose.
 */
protected void disposeGC(GC gc) {
	gc.dispose();
}
/** 
 * Draws the line delimiter selection if the selection extends beyond the given line.
 * </p>
 *
 * @param line the line to draw
 * @param lineOffset offset of the first character in the line.
 * 	Relative to the start of the document.
 * @param styles line styles
 * @param paintY y location to draw at
 * @param gc GC to draw on
 */
protected void drawLineBreakSelection(char[] line, int lineOffset, int paintX, int paintY, GC gc) {
	Point selection = parent.internalGetSelection();
	int lineLength = line.length;
	int selectionStart = Math.max(0, selection.x - lineOffset);
	int selectionEnd = selection.y - lineOffset;
	int lineEndSpaceWidth = getLineEndSpaceWidth();
	int lineHeight = getLineHeight();
	
	if (selectionEnd == selectionStart || selectionEnd < 0 || selectionStart > lineLength || selectionEnd <= lineLength) {
		return;
	}
	
	gc.setBackground(parent.getSelectionBackground());
	gc.setForeground(parent.getSelectionForeground());
	if ((parent.getStyle() & DWT.FULL_SELECTION) != 0) {
		Rectangle rect = getClientArea();
		gc.fillRectangle(paintX, paintY, rect.width - paintX, lineHeight);
	} else {
		boolean isWrappedLine = false;
		if (parent.internalGetWordWrap()) {
			StyledTextContent content = getContent();
			int lineEnd = lineOffset + lineLength;
			int lineIndex = content.getLineAtOffset(lineEnd);

			// is the start offset of the next line the same as the end 
			// offset of this line?
			if (lineIndex < content.getLineCount() - 1 &&
				content.getOffsetAtLine(lineIndex + 1) == lineEnd) {
				isWrappedLine = true;
			}
		}
		if (isWrappedLine == false) {
			// render the line break selection
			gc.fillRectangle(paintX, paintY, lineEndSpaceWidth, lineHeight);
		}
	}	
}
/**
 * Returns the text segments that should be treated as if they 
 * had a different direction than the surrounding text.
 * </p>
 *
 * @param lineOffset offset of the first character in the line. 
 * 	0 based from the beginning of the document.
 * @param line text of the line to specify bidi segments for
 * @return text segments that should be treated as if they had a
 * 	different direction than the surrounding text. Only the start 
 * 	index of a segment is specified, relative to the start of the 
 * 	line. Always starts with 0 and ends with the line length. 
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the segment indices returned 
 * 		by the listener do not start with 0, are not in ascending order,
 * 		exceed the line length or have duplicates</li>
 * </ul>
 */
protected int[] getBidiSegments(int lineOffset, char[] lineText) {
	if (!parent.isBidi()) return null;
	return parent.getBidiSegments(lineOffset, lineText);
}
/**
 * Returns the visible client area that can be used for rendering.
 * </p>
 * @return the visible client area that can be used for rendering.
 */
protected Rectangle getClientArea() {
	return parent.getClientArea();
}
/**
 * Returns the <class>StyledTextContent</class> to use for line offset
 * calculations.
 * </p>
 * @return the <class>StyledTextContent</class> to use for line offset
 * calculations.
 */
protected StyledTextContent getContent() {
	return parent.internalGetContent();
}
/**
 * Returns a new GC to use for rendering and measuring.
 * When the GC is no longer used it needs to be disposed by 
 * calling disposeGC.
 * </p>
 * @return the GC to use for rendering and measuring.
 * @see disposeGC
 */
protected GC getGC() {
	return new GC(parent);
}
/**
 * Returns the horizontal scroll position.
 * </p>
 * @return the horizontal scroll position.
 */
protected int getHorizontalPixel() {
	return parent.internalGetHorizontalPixel();
}
protected int getLeftMargin() {
	return parent.leftMargin;
}
/**
 * @see StyledTextRenderer#getLineBackgroundData
 */
// NOTE: change from protected to public
public StyledTextEvent getLineBackgroundData(int lineOffset, char[] line) {
	return parent.getLineBackgroundData(lineOffset, line);
}
/**
 * @see StyledTextRenderer#getLineStyleData
 */
public StyledTextEvent getLineStyleData(int lineOffset, char[] line) {
	StyledTextEvent logicalLineEvent = parent.getLineStyleData(lineOffset, line);
	if (logicalLineEvent !is null) {
		logicalLineEvent = super.getLineStyleData(logicalLineEvent, lineOffset, line);
	}
	return logicalLineEvent;
}
protected  int getOrientation () {
	return parent.getOrientation();
}
protected int getRightMargin() {
	return parent.rightMargin;
}
protected Color getSelectionBackground() {
	return parent.getSelectionBackground();
}
protected Color getSelectionForeground() {
	return parent.getSelectionForeground();
}
/**
 * @see StyledTextRenderer#getSelection
 */
protected Point getSelection() {
	return parent.internalGetSelection();
}
/**
 * @see StyledTextRenderer#getWordWrap
 */
protected boolean getWordWrap() {
	return parent.getWordWrap();
}
/**
 * @see StyledTextRenderer#isFullLineSelection
 */
protected boolean isFullLineSelection() {
	return cast(boolean)((parent.getStyle() & DWT.FULL_SELECTION) != 0);
}
TextLayout createTextLayout(int lineOffset) {
	if (!parent.internalGetWordWrap()) {
		int lineIndex = getContent().getLineAtOffset(lineOffset);
		updateTopIndex();
		if (layouts !is null) {
			int layoutIndex = lineIndex - topIndex;
			if (0 <= layoutIndex && layoutIndex < layouts.length) {
				TextLayout layout = layouts[layoutIndex];
				if (layout !is null) return layout;
				return layouts[layoutIndex] = super.createTextLayout(lineIndex); 
			}
		}
	}
	return super.createTextLayout(lineOffset);
}
void disposeTextLayout (TextLayout layout) {
	if (layouts !is null) {
		for (int i=0; i<layouts.length; i++) {
			if (layouts[i] is layout) return;
		}
	}
	super.disposeTextLayout(layout);
}
void updateTopIndex() {
	int verticalIncrement = parent.getVerticalIncrement();
	int topIndex = verticalIncrement == 0 ? 0 : parent.verticalScrollOffset / verticalIncrement;
	int newLength = Math.max(1 , parent.getPartialBottomIndex() - topIndex + 1);
	if (layouts is null || topIndex != this.topIndex || newLength != layouts.length) {
		TextLayout[] newLayouts = new TextLayout[newLength];
		if (layouts !is null) {
			for(int i=0; i<layouts.length; i++) {
				TextLayout layout = layouts[i];
				if (layout !is null) {
					int layoutIndex = (i + this.topIndex) - topIndex;
					if (0 <= layoutIndex && layoutIndex < newLayouts.length) {
						newLayouts[layoutIndex] = layout;
					} else {
						super.disposeTextLayout(layout);
					}
				}
			}
		}
		this.topIndex = topIndex;
		layouts = newLayouts;
	}
}
}


/**
 * A PrintRenderer renders the content of a StyledText widget on 
 * a printer device.
 * Print rendering may occur in a non-UI thread. Therefore all 
 * requests for styles, content and any other information normally 
 * stored in the StyledText widget are served from cached data.
 * Caching also guarantees immutable data for threaded printing.
 */
class PrintRenderer : StyledTextRenderer {
	StyledTextContent logicalContent;		// logical, unwrapped, content
	WrappedContent content;					// wrapped content
	Rectangle clientArea;					// printer client area
	GC gc;									// printer GC, there can be only one GC for each printer device
	Hashtable!(Integer, StyledTextEvent) lineBackgrounds;				// line background colors used during rendering
	Hashtable!(Integer, StyledTextEvent) lineStyles;					// line styles colors used during rendering
	Hashtable!(Integer, int[]) bidiSegments;			 		// bidi segments used during rendering on bidi platforms
	
/**
 * Creates an instance of <class>PrintRenderer</class>.
 * </p>
 * @param device Device to render on
 * @param regularFont Font to use for regular (non-bold) text.
 * @param gc printer GC to use for rendering. There can be only one GC for 
 * 	each printer device at any given time.
 * @param logicalContent StyledTextContent to print.
 * @param lineBackgrounds line background colors to use during rendering.
 * @param lineStyles line styles colors to use during rendering.
 * @param bidiSegments bidi segments to use during rendering on bidi platforms.
 * @param leftMargin margin to the left of the text.
 * @param tabLength length in characters of a tab character
 * @param clientArea the printer client area.
 */
this (
		Device device, Font regularFont, GC gc, 
		StyledTextContent logicalContent, Hashtable!(Integer, StyledTextEvent) lineBackgrounds, 
		Hashtable!(Integer, StyledTextEvent) lineStyles, Hashtable!(Integer, int[]) bidiSegments,
		int tabLength, Rectangle clientArea) {
	super(device, regularFont);
	this.logicalContent = logicalContent;
	this.lineBackgrounds = lineBackgrounds;
	this.lineStyles = lineStyles;
	this.bidiSegments = bidiSegments;	
	this.clientArea = clientArea;	
	this.gc = gc;
	calculateLineHeight();
	setTabLength(tabLength);
	content = new WrappedContent(this, logicalContent);
	// wrapLines requires tab width to be known	
	content.wrapLines();
}
/**
 * Disposes the resource created by the receiver.
 */
package void dispose() {
	content = null;
	super.dispose();
}
/**
 * Do nothing. PrintRenderer does not create GCs.
 * @see StyledTextRenderer#disposeGC
 */
protected void disposeGC(GC gc) {
}
/** 
 * Do not print the selection.
 * @see StyledTextRenderer#drawLineSelectionBackground
 */
protected void drawLineBreakSelection(char[] line, int lineOffset, int paintX, int paintY, GC gc) {
}
/**
 * Returns from cache the text segments that should be treated as 
 * if they had a different direction than the surrounding text.
 * <p>
 * Use cached data.
 * </p>
 *
 * @param lineOffset offset of the first character in the line. 
 * 	0 based from the beginning of the document.
 * @param line text of the line to specify bidi segments for
 * @return text segments that should be treated as if they had a
 * 	different direction than the surrounding text. Only the start 
 * 	index of a segment is specified, relative to the start of the 
 * 	line. Always starts with 0 and ends with the line length. 
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the segment indices returned 
 * 		by the listener do not start with 0, are not in ascending order,
 * 		exceed the line length or have duplicates</li>
 * </ul>
 */
protected int[] getBidiSegments(int lineOffset, char[] lineText) {
	int lineLength = lineText.length;
	int logicalLineOffset = getLogicalLineOffset(lineOffset);
	int[] segments = cast(int [])bidiSegments.get(new Integer(logicalLineOffset));
	
	if (segments is null) {
		int[] intArr;
		intArr ~= 0;
		intArr ~= lineLength;
		segments = intArr;
	}
	else {
		// cached bidi segments are for logical lines.
		// make sure that returned segments match requested line since
		// line wrapping may require either entire or part of logical 
		// line bidi segments
		int logicalLineIndex = logicalContent.getLineAtOffset(lineOffset);
		int logicalLineLength = logicalContent.getLine(logicalLineIndex).length;
		
		if (lineOffset != logicalLineOffset || lineLength != logicalLineLength) {
			int lineOffsetDelta = lineOffset - logicalLineOffset;
			int newSegmentCount = 0;
			int[] newSegments = new int[segments.length];
			
			for (int i = 0; i < segments.length; i++) {
				newSegments[i] = Math.max(0, segments[i] - lineOffsetDelta);
				if (newSegments[i] > lineLength) {
					newSegments[i] = lineLength;
					newSegmentCount++;
					break;
				}
				if (i == 0 || newSegments[i] > 0) {
					newSegmentCount++;
				}
			}
			segments = new int[newSegmentCount];
			for (int i = 0, newIndex = 0; i < newSegments.length && newIndex < newSegmentCount; i++) {
				if (i == 0 || newSegments[i] > 0) {
					segments[newIndex++] = newSegments[i];
				}
			}
		}
	}
	return segments;
}
/**
 * Returns the printer client area.
 * </p>
 * @return the visible client area that can be used for rendering.
 * @see StyledTextRenderer#getClientArea
 */
protected Rectangle getClientArea() {
	return clientArea;
}
/**
 * Returns the <class>StyledTextContent</class> to use for line offset
 * calculations.
 * This is the wrapped content, calculated in the constructor from the 
 * logical printing content.
 * </p>
 * @return the <class>StyledTextContent</class> to use for line offset
 * calculations.
 */
 // TODO:
 // <Shawn> parent func is public ???
public StyledTextContent getContent() {
	return content;
}
/**
 * Returns the printer GC to use for rendering and measuring.
 * There can be only one GC for each printer device at any given
 * time.
 * </p>
 * @return the printer GC to use for rendering and measuring.
 */
protected GC getGC() {
	return gc;
}
/**
 * Returns 0. Scrolling does not affect printing. Text is wrapped
 * for printing.
 * </p>
 * @return 0
 * @see StyledTextRenderer#getHorizontalPixel
 */
protected int getHorizontalPixel() {
	return 0;
}
/**
 * Returns the start offset of the line at the given offset.
 * </p>
 * @param visualLineOffset an offset that may be anywhere within a 
 * 	line.
 * @return the start offset of the line at the given offset, 
 * 	relative to the start of the document.
 */
private int getLogicalLineOffset(int visualLineOffset) {
	int logicalLineIndex = logicalContent.getLineAtOffset(visualLineOffset);
	
	return logicalContent.getOffsetAtLine(logicalLineIndex);
}
protected  int getOrientation () {
	return DWT.LEFT_TO_RIGHT;
}
protected Color getSelectionBackground() {
	return null;
}
protected Color getSelectionForeground() {
	return null;
}
/**
 * Return cached line background data.
 * @see StyledTextRenderer#getLineBackgroundData
 */
// NOTE: change from protected to public
public StyledTextEvent getLineBackgroundData(int lineOffset, char[] line) {
	int logicalLineOffset = getLogicalLineOffset(lineOffset);
	
	return cast(StyledTextEvent)lineBackgrounds.get(new Integer(logicalLineOffset));
}
/**
 * Return cached line style background data.
 * @see StyledTextRenderer#getLineStyleData
 */
public StyledTextEvent getLineStyleData(int lineOffset, char[] line) {
	int logicalLineOffset = getLogicalLineOffset(lineOffset);
	StyledTextEvent logicalLineEvent = cast(StyledTextEvent)lineStyles.get(new Integer(logicalLineOffset));
	
	if (logicalLineEvent !is null) {
		StyledTextEvent clone = new StyledTextEvent(cast(StyledTextContent) logicalLineEvent.data);
		clone.detail = logicalLineEvent.detail;
		clone.styles = logicalLineEvent.styles;
		clone.text = logicalLineEvent.text;
		logicalLineEvent = super.getLineStyleData(clone, lineOffset, line);
	}
	return logicalLineEvent;
}
/** 
 * Selection is not printed.
 * </p>
 * @return Point(0,0)
 * @see StyledTextRenderer#getSelection
 */
protected Point getSelection() {
	return new Point(0, 0);
}
/**
 * Printed content is always wrapped.
 * </p>
 * @return true
 * @see StyledTextRenderer#getWordWrap
 */
protected boolean getWordWrap() {
	return true;
}
/**
 * Selection is not printed. Returns false.
 * <p>
 * @return false
 * @see StyledTextRenderer#isFullLineSelection
 */
protected boolean isFullLineSelection() {
	return false;
}
}

/**
 * This event is sent to BidiSegmentListeners when a line is to
 * be measured or rendered in a bidi locale.  The segments field is 
 * used to specify text ranges in the line that should be treated as 
 * separate segments for bidi reordering.  Each segment will be reordered 
 * and rendered separately.
 * <p>
 * The elements in the segments field specify the start offset of 
 * a segment relative to the start of the line. They must follow
 * the following rules:
 * <ul>
 * <li>first element must be 0
 * <li>elements must be in ascending order and must not have duplicates
 * <li>elements must not exceed the line length
 * </ul>
 * In addition, the last element may be set to the end of the line 
 * but this is not required.
 *
 * The segments field may be left null if the entire line should 
 * be reordered as is.
 * </p>
 * A BidiSegmentListener may be used when adjacent segments of 
 * right-to-left text should not be reordered relative to each other. 
 * For example, within a Java editor, you may wish multiple 
 * right-to-left string literals to be reordered differently than the
 * bidi algorithm specifies.  
 *
 * Example:
 * <pre>
 * 	stored line = "R1R2R3" + "R4R5R6"
 *		R1 to R6 are right-to-left characters. The quotation marks
 * 		are part of the line text. The line is 13 characters long.
 * 
 * 	segments = null: 
 * 		entire line will be reordered and thus the two R2L segments 
 * 		swapped (as per the bidi algorithm). 
 *		visual line (rendered on screen) = "R6R5R4" + "R3R2R1"
 * 
 * 	segments = [0, 5, 8]	
 * 		"R1R2R3" will be reordered, followed by [blank]+[blank] and 
 * 		"R4R5R6". 
 *		visual line = "R3R2R1" + "R6R5R4"
 * </pre>
 */
public class BidiSegmentEvent : TypedEvent {
	/** line start offset */
	public int lineOffset;
	/** line text */			
	public char[] lineText;
	/** bidi segments, see above */
	public int[] segments;			

this(StyledTextEvent e) {
	super(cast(Object)e);
	lineOffset = e.detail;
	lineText = e.text;
}
}

/**
 * This listener interface may be implemented in order to receive
 * BidiSegmentEvents.
 * @see BidiSegmentEvent
 */ 
public interface BidiSegmentListener : DWTEventListener {

/**
 * This method is called when a line needs to be reordered for 
 * measuring or rendering in a bidi locale. 
 * <p>
 *
 * @param event.lineOffset line start offset (input)	
 * @param event.lineText line text (input)
 * @param event.segments text segments that should be reordered 
 *	separately. (output)
 * @see BidiSegmentEvent
 */
public void lineGetSegments(BidiSegmentEvent event);

}

class DefaultContent : StyledTextContent {
//	private static String LineDelimiter = System.getProperty("line.separator");
	private static char[] LineDelimiter() { return ("\r\n"); }

	Vector!(StyledTextListener) textListeners; // stores text listeners for event sending
	wchar[] textStore;// = new wchar[0];	// stores the actual text
	int gapStart = -1;	// the character position start of the gap
	int gapEnd = -1;	// the character position after the end of the gap
	int gapLine = -1;	// the line on which the gap exists, the gap will always be associated
				// with one line
	int highWatermark = 300;	
	int lowWatermark = 50;		

	// TODO:
//	int[][] lines = new int[50][2];	// array of character positions and lengths representing
	RANGE[] lines;
							// the lines of text
	int lineCount_var = 0;	// the number of lines of text	
	int expandExp = 1; 	// the expansion exponent, used to increase the lines array exponentially
	int replaceExpandExp = 1; 	// the expansion exponent, used to increase the lines array exponentially

/** 
 * Creates a new DefaultContent and initializes it.  A <code>StyledTextContent</> will always have
 * at least one empty line.
 */
this() {
	textListeners = new Vector!(StyledTextListener);
	lines = new RANGE[50];
	setText("");
}
/** 
 * Adds a line to the end of the line indexes array.  Increases the size of the array if necessary.
 * <code>lineCount</code> is updated to reflect the new entry.
 * <p>
 *
 * @param start the start of the line
 * @param length the length of the line
 */
void addLineIndex(int start, int length) {
	int len = lines.length;
	if (lineCount_var == len) {
		// expand the lines by powers of 2
		RANGE[] newLines = new RANGE[len+Compatibility.pow2(expandExp)];
		SimpleType!(RANGE).arraycopy(lines, 0, newLines, 0, len);
		lines = newLines;
		expandExp++;
	}
	RANGE range;
	range.Position = start;
	range.Length = length;
	lines[lineCount_var] = range;
	lineCount_var++;
}
/** 
 * Adds a line index to the end of <code>linesArray</code>.  Increases the 
 * size of the array if necessary and returns a new array.
 * <p>
 *
 * @param start the start of the line
 * @param length the length of the line
 * @param linesArray the array to which to add the line index
 * @param count the position at which to add the line
 * @return a new array of line indexes
 */
RANGE[] addLineIndex(int start, int length, RANGE[] linesArray, int count) {
	int size = linesArray.length;
	RANGE[] newLines = linesArray;
	if (count == size) {
		newLines = new RANGE[size+Compatibility.pow2(replaceExpandExp)];
		replaceExpandExp++;
		SimpleType!(RANGE).arraycopy(linesArray, 0, newLines, 0, size);
	}
	RANGE range;
	range.Position = start;
	range.Length = length;
	newLines[count] = range;
	return newLines;
}
/**
 * Adds a <code>TextChangeListener</code> listening for 
 * <code>TextChangingEvent</code> and <code>TextChangedEvent</code>. A 
 * <code>TextChangingEvent</code> is sent before changes to the text occur.
 * A <code>TextChangedEvent</code> is sent after changes to the text 
 * occured.
 * <p>
 *
 * @param listener the listener
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
 * </ul>
 */
public void addTextChangeListener(TextChangeListener listener) {
	if (listener is null) error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
	StyledTextListener typedListener = new StyledTextListener(listener);
	textListeners.addElement(typedListener);	
}	
/**
 * Adjusts the gap to accomodate a text change that is occurring.
 * <p>
 *
 * @param position the position at which a change is occurring
 * @param sizeHint the size of the change
 * @param line the line where the gap will go
 */
void adjustGap(int position, int sizeHint, int line) {
	if (position == gapStart) {
		// text is being inserted at the gap position
		int size = (gapEnd - gapStart) - sizeHint;
		if (lowWatermark <= size && size <= highWatermark)
			return;
	} else if ((position + sizeHint == gapStart) && (sizeHint < 0)) {
		// text is being deleted at the gap position
		int size = (gapEnd - gapStart) - sizeHint;
		if (lowWatermark <= size && size <= highWatermark)
			return;
	}
	moveAndResizeGap(position, sizeHint, line);
}
/**
 * Calculates the indexes of each line in the text store.  Assumes no gap exists.  
 * Optimized to do less checking.
 */
void indexLines(){
	int start = 0;
	lineCount_var = 0;
	int textLength = textStore.length;
	int i;
	for (i=start; i<textLength; i++) { 
		wchar ch = textStore[i];					
		if (ch == DWT.WCR) {
			// see if the next character is a LF
			if (i + 1 < textLength) {
				ch = textStore[i+1];
				if (ch == DWT.WLF) {
					i++;
				}
			}
			addLineIndex(start, i - start + 1);
			start = i + 1;
		} else if (ch == DWT.WLF) {
			addLineIndex(start, i - start + 1);
			start = i + 1;
		} 
	}			
	addLineIndex(start, i - start);
}
/** 
 * Returns whether or not the given character is a line delimiter.  Both CR and LF 
 * are valid line delimiters.
 * <p>
 *
 * @param ch the character to test
 * @return true if ch is a delimiter, false otherwise
 */
boolean isDelimiter(wchar ch) {
	if (ch == DWT.WCR) return true;
	if (ch == DWT.WLF) return true;
	return false;
}	
/**
 * Determine whether or not the replace operation is valid.  DefaultContent will not allow
 * the /r/n line delimiter to be split or partially deleted.
 * <p>
 *
 * @param start	start offset of text to replace
 * @param replaceLength start offset of text to replace
 * @param newText start offset of text to replace
 */
protected boolean isValidReplace(int start, int replaceLength, char[] newText){
	if (replaceLength == 0) {
		// inserting text, see if the \r\n line delimiter is being split
		if (start == 0) return true;
		if (start == getCharCount()) return true;
		char before = getTextRange(start - 1, 1)[0];
		if (before == '\r') {
			char after = getTextRange(start, 1)[0];
			if (after == '\n') return false;
		}
	} else {
		// deleting text, see if part of a \r\n line delimiter is being deleted
		char startChar = getTextRange(start, 1)[0];
		if (startChar == '\n') {
			// see if wchar before delete position is \r
			if (start != 0) {
				char before = getTextRange(start - 1, 1)[0];
				if (before == '\r') return false;
			}
		}
		char endChar = getTextRange(start + replaceLength - 1, 1)[0];
		if (endChar == '\r') {
			// see if wchar after delete position is \n
			if (start + replaceLength != getCharCount()) {
				wchar after = getTextRange(start + replaceLength, 1)[0];
				if (after == '\n') return false;
			}
		}
	} 
	return true;
}
/**
 * Calculates the indexes of each line of text in the given range.
 * <p>
 *
 * @param offset the logical start offset of the text lineate
 * @param length the length of the text to lineate, includes gap
 * @param numLines the number of lines to initially allocate for the line index array,
 *	passed in for efficiency (the exact number of lines may be known)
 * @return a line indexes array where each line is identified by a start offset and 
 * 	a length
 */
RANGE[] indexLines(int offset, int length, int numLines){
	RANGE[] indexedLines = new RANGE[numLines];
	int start = 0;
	int lineCnt = 0;
	int i;
	replaceExpandExp = 1;
	for (i=start; i<length; i++) {
		int location = i + offset; 
		if ((location >= gapStart) && (location < gapEnd)) {
			// ignore the gap
		} else {
			wchar ch = textStore[location];				
			if (ch == DWT.WCR) {
				// see if the next character is a LF
				if (location+1 < textStore.length) {
					ch = textStore[location+1];
					if (ch == DWT.WLF) {
						i++;
					} 
				}
				indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCnt);
				lineCnt++;
				start = i + 1;
			} else if (ch == DWT.WLF) {
				indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCnt);
				lineCnt++;
				start = i + 1;
			} 
		}
	}
	RANGE[] newLines = new RANGE[lineCnt+1];
	SimpleType!(RANGE).arraycopy(indexedLines, 0, newLines, 0, lineCnt);

	RANGE range;
	range.Position = start;
	range.Length = i-start;
	newLines[lineCnt]=range;
	return newLines; 
}
/**
 * Inserts text.
 * <p>
 *
 * @param position the position at which to insert the text
 * @param length the text to insert
 */
void insert(int position, char[] str) {	
	if (str.length == 0) return;
				
	int startLine = getLineAtOffset(position);
	// Important !!!
	// <Shawn> convert to wchar first
	wchar[] text = Utf.toString16(str);
	int change = text.length;
	boolean endInsert = cast(boolean)(position == getCharCount());
	adjustGap(position, change, startLine);

	// during an insert the gap will be adjusted to start at
	// position and it will be associated with startline, the
	// inserted text will be placed in the gap		
	int startLineOffset = getOffsetAtLine(startLine);
	// at this point, startLineLength will include the start line
	// and all of the newly inserted text
	int	startLineLength = getPhysicalLine(startLine).length;
	
	if (change > 0) {
		// shrink gap 
		gapStart += (change);
		for (int i = 0; i < text.length; i++)
			textStore[position + i] = text[i];
	}
		
	// figure out the number of new lines that have been inserted
	RANGE[] newLines = indexLines(startLineOffset, startLineLength, 10);
	// only insert an empty line if it is the last line in the text
	int numNewLines = newLines.length - 1;
	if (newLines[numNewLines][1] == 0) {
		// last inserted line is a new line
		if (endInsert) {
			// insert happening at end of the text, leave numNewLines as
			// is since the last new line will not be concatenated with another
			// line 
			numNewLines += 1;
		} else {
			numNewLines -= 1;
		}
	}
	
	// make room for the new lines
	expandLinesBy(numNewLines);
	// shift down the lines after the replace line
	for (int i = lineCount_var-1; i > startLine; i--) {
		lines[i + numNewLines]=lines[i];
	}
	// insert the new lines
	for (int i=0; i<numNewLines; i++) {
		newLines[i].Position += startLineOffset;
		lines[startLine + i]=newLines[i];
	}
	// update the last inserted line
	if (numNewLines < newLines.length) {
		newLines[numNewLines].Position += startLineOffset;
		lines[startLine + numNewLines] = newLines[numNewLines];
	}
	
	lineCount_var += numNewLines;
	gapLine = getLineAtPhysicalOffset(gapStart);		
}
/**
 * Moves the gap and adjusts its size in anticipation of a text change.  
 * The gap is resized to actual size + the specified size and moved to the given 
 * position.
 * <p>
 *
 * @param position the position at which a change is occurring
 * @param sizeHint the size of the change
 * @param line the line where the gap should be put
 */
void moveAndResizeGap(int position, int size, int newGapLine) {
	wchar[] content = null;
	int oldSize = gapEnd - gapStart;
	int newSize;
	if (size > 0) {
		newSize = highWatermark + size;
	} else {
		newSize = lowWatermark - size;
	}
	// remove the old gap from the lines information
	if (gapExists()) {
		// adjust the line length
		lines[gapLine][1] = lines[gapLine][1] - oldSize;
		// adjust the offsets of the lines after the gapLine
		for (int i=gapLine+1; i<lineCount_var; i++) {
			lines[i][0]=lines[i][0]-oldSize;
		}
	}
	
	if (newSize < 0) {
		if (oldSize > 0) {
			// removing the gap
			content = new wchar[textStore.length - oldSize];
			System.arraycopy(textStore, 0, content, 0, gapStart);
			System.arraycopy(textStore, gapEnd, content, gapStart, content.length - gapStart);
			textStore= content;
		}
		gapStart = gapEnd= position;
		return;
	}
	content = new wchar[textStore.length + (newSize - oldSize)];
	int newGapStart = position;
	int newGapEnd = newGapStart + newSize;
	if (oldSize == 0) {		
		System.arraycopy(textStore, 0, content, 0, newGapStart);
		System.arraycopy(textStore, newGapStart, content, newGapEnd, content.length - newGapEnd);	
	} else if (newGapStart < gapStart) {
		int delta = gapStart - newGapStart;
		System.arraycopy(textStore, 0, content, 0, newGapStart);
		System.arraycopy(textStore, newGapStart, content, newGapEnd, delta);
		System.arraycopy(textStore, gapEnd, content, newGapEnd + delta, textStore.length - gapEnd);
	} else {		
		int delta = newGapStart - gapStart;
		System.arraycopy(textStore, 0, content, 0, gapStart);
		System.arraycopy(textStore, gapEnd, content, gapStart, delta);
		System.arraycopy(textStore, gapEnd + delta, content, newGapEnd, content.length - newGapEnd);
	}
	textStore = content;
	gapStart = newGapStart;
	gapEnd = newGapEnd;
	
	// add the new gap to the lines information
	if (gapExists()) {
		gapLine = newGapLine;
		// adjust the line length
		int gapLength = gapEnd - gapStart;
		lines[gapLine][1] = lines[gapLine][1] + (gapLength);
		// adjust the offsets of the lines after the gapLine
		for (int i=gapLine+1; i<lineCount_var; i++) {
			lines[i][0]=lines[i][0]+gapLength;
		}
	}
}
/** 
 * Returns the number of lines that are in the specified text.
 * <p>
 *
 * @param startOffset the start of the text to lineate
 * @param length the length of the text to lineate
 * @return number of lines
 */
int lineCount(int startOffset, int length){
	if (length == 0) {
		return 0;
	}
	int lineCnt = 0;
	int count = 0;
	int i = startOffset;
	if (i >= gapStart) {
		i += gapEnd - gapStart;
	}
	while (count < length) {
		if ((i >= gapStart) && (i < gapEnd)) {
			// ignore the gap
		} else {
			wchar ch = textStore[i];			
			if (ch == DWT.WCR) {
				// see if the next character is a LF
				if (i + 1 < textStore.length) {
					ch = textStore[i+1];
					if (ch == DWT.WLF) {
						i++;
						count++;
					} 
				} 
				lineCnt++;
			} else if (ch == DWT.WLF) {
				lineCnt++;
			}
			count++;
		}
		i++;
	}			
	return lineCnt;
}
/** 
 * Returns the number of lines that are in the specified text.
 * <p>
 *
 * @param text the text to lineate
 * @return number of lines in the text
 */
int lineCount(char[] text){
	int lineCount_var = 0;
	int length = text.length;
	for (int i = 0; i < length; i++) {
		wchar ch = text[i];
		if (ch == DWT.WCR) {
			if (i + 1 < length && text[i + 1] == DWT.WLF) {
				i++;
			}
			lineCount_var++;
		}
		else
		if (ch == DWT.WLF) {
			lineCount_var++;
		}
	}
	return lineCount_var;	
}
/**
 * @return the logical length of the text store
 */
public int getCharCount() {
	int length = gapEnd - gapStart;
	return (textStore.length - length);
}
/**
 * Returns the line at <code>index</code> without delimiters.
 * <p>
 *
 * @param index	the index of the line to return
 * @return the logical line text (i.e., without the gap)
 * @exception IllegalArgumentException <ul>
 *   <li>ERROR_INVALID_ARGUMENT when index is out of range</li>
 * </ul>
 */
public char[] getLine(int index) {
	if ((index >= lineCount_var) || (index < 0)) error(__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);
	int start = lines[index][0];
	int len = lines[index][1];
	int end = start + len - 1;
	if (!gapExists() || (end < gapStart) || (start >= gapEnd)) {
		// line is before or after the gap
		while ((len - 1 >= 0) && isDelimiter(textStore[start+len-1])) {
			len--;
		}
		return Utf.toString(textStore[start..start+len]);
	} else {
		// gap is in the specified range, strip out the gap
		wchar[] buf;
		int gapLength = gapEnd - gapStart;
		buf ~= textStore[start..gapStart];
		buf ~= textStore[gapEnd..len + start];
		len = buf.length;
		while ((len - 1 >=0) && isDelimiter(buf[len-1])) {
			len--;
		}
		return Utf.toString(buf);
	}
}
/**
 * Returns the line delimiter that should be used by the StyledText 
 * widget when inserting new lines.  This delimiter may be different than the
 * delimiter that is used by the <code>StyledTextContent</code> interface.
 * <p>
 *
 * @return the platform line delimiter as specified in the line.separator
 * 	system property.
 */
public char[] getLineDelimiter() {
	return LineDelimiter;
}
/**
 * Returns the line at the given index with delimiters.
 * <p>
 * @param index	the index of the line to return
 * @return the logical line text (i.e., without the gap) with delimiters
 */
char[] getFullLine(int index) {
	int start = lines[index][0];
	int len = lines[index][1];
	int end = start + len - 1;
	if (!gapExists() || (end < gapStart) || (start >= gapEnd)) {
		// line is before or after the gap
		return Utf.toString(textStore[start..start+len]);
	} else {
		// gap is in the specified range, strip out the gap
		wchar[] buf2;
		buf2 ~= textStore[start..gapStart];
		buf2 ~= textStore[gapEnd.. len+start];
		
		return Utf.toString(buf2);
	}
}
/**
 * Returns the physical line at the given index (i.e., with delimiters and the gap).
 * <p>
 *
 * @param index the line index
 * @return the physical line 
 */
char[] getPhysicalLine(int index) {
	int start = lines[index][0];
	int length = lines[index][1];
	return getPhysicalText(start, length);
}
/**
 * @return the number of lines in the text store
 */
public int getLineCount(){
	return lineCount_var;
}
/**
 * Returns the line at the given offset.
 * <p>
 *
 * @param charPosition logical character offset (i.e., does not include gap)
 * @return the line index
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT when charPosition is out of range</li>
 * </ul>
 */
public int getLineAtOffset(int charPosition){
	int position;
	if ((charPosition > getCharCount()) || (charPosition < 0)) error(__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);
	if (charPosition < gapStart) {
		// position is before the gap
		position = charPosition;
	} else {
		// position includes the gap
		position = charPosition + (gapEnd - gapStart);
	} 			

	// if last line and the line is not empty you can ask for 
	// a position that doesn't exist (the one to the right of the 
	// last character) - for inserting
	if (lineCount_var > 0) {
		int lastLine = lineCount_var - 1;
		if (position == lines[lastLine][0] + lines[lastLine][1]) 
			return lastLine;
	}

	int high = lineCount_var;
	int low = -1;
	int index = lineCount_var;
	while (high - low > 1) {
		index = (high + low) / 2;
		int lineStart = lines[index][0];
		int lineEnd = lineStart + lines[index][1] - 1;
		if (position <= lineStart) {
			high = index;			
		} else if (position <= lineEnd) {
			high = index;
			break;
		} else {
			low = index;
		}
	}

	return high;
}
/**
 * Returns the line index at the given physical offset.
 * <p>
 *
 * @param position physical character offset (i.e., includes gap)
 * @return the line index
 */
int getLineAtPhysicalOffset(int position){
	int high = lineCount_var;
	int low = -1;
	int index = lineCount_var;
	while (high - low > 1) {
		index = (high + low) / 2;
		int lineStart = lines[index][0];
		int lineEnd = lineStart + lines[index][1] - 1;
		if (position <= lineStart) {
			high = index;			
		} else if (position <= lineEnd) {
			high = index;
			break;
		} else {
			low = index;
		}
	}
	return high;
}
/**
 * Returns the logical offset of the given line.
 * <p>
 *
 * @param lineIndex index of line 
 * @return the logical starting offset of the line.  When there are not any lines,
 * 	getOffsetAtLine(0) is a valid call that should answer 0.
 * @exception IllegalArgumentException <ul>
 *   <li>ERROR_INVALID_ARGUMENT when lineIndex is out of range</li>
 * </ul>
 */
public int getOffsetAtLine(int lineIndex) {
	if (lineIndex == 0) return 0;
	if ((lineIndex >= lineCount_var) || (lineIndex < 0)) error(__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);
	int start = lines[lineIndex][0];
	if (start > gapEnd) {
		return start - (gapEnd - gapStart);
	} else {
		return start;
	}
}	
/**
 * Increases the line indexes array to accomodate more lines.
 * <p>
 *
 * @param numLines the number to increase the array by
 */
void expandLinesBy(int numLines) {
	int size = lines.length;
	if (size - lineCount_var >= numLines) {
		return;
	}
	RANGE[] newLines = new RANGE[size+Math.max(10, numLines)];
	SimpleType!(RANGE).arraycopy(lines, 0, newLines, 0, size);
	lines = newLines;
}
/**	 
 * Reports an DWT error.
 * <p>
 *
 * @param code the error code
 */

 alias DWT.error error;
//void error (String fileName, int line, int code) {
//	DWT.error(fileName, line, code);
//}
/** 
 * Returns whether or not a gap exists in the text store.
 * <p>
 *
 * @return true if gap exists, false otherwise
 */
boolean gapExists() {
	return cast(boolean)(gapStart != gapEnd);
}	

/**
 * Returns a string representing the continous content of
 * the text store.
 * <p>
 *
 * @param start	the physical start offset of the text to return
 * @param length the physical length of the text to return
 * @return the text
 */

char[] getPhysicalText(int start, int len) {
	Util.trace((__FILE__) , ", textStore.length = " , textStore.length);
	Util.trace((__FILE__) , ", textStore[start..start+len].length = " , textStore[start..start+len].length);
	wchar[] text = textStore[start..start+len];
	OS.OutputDebugStringW(text.ptr);
	for(int i = start; i<start+len; ++i) {
		wchar c = cast(dchar)textStore[i];
		if(c == 0xFFFF)
			textStore[i+start] = 0;
	}

	text = textStore[start..start+len];
	OS.OutputDebugStringW(text.ptr);
	return Utf.toString(textStore[start..start+len]);
}
/**
 * Returns a string representing the logical content of
 * the text store (i.e., gap stripped out).
 * <p>
 *
 * @param start the logical start offset of the text to return
 * @param length the logical length of the text to return
 * @return the text
 */
public char[] getTextRange(int start, int len) {
	if (textStore is null)
		return "";
	if (len == 0)
		return "";
	int end= start + len;
	if (!gapExists() || (end < gapStart))
		return Utf.toString(textStore[start..start+len]);
	if (gapStart < start) {
		int gapLength= gapEnd - gapStart;
		return Utf.toString(textStore[start + gapLength..start + gapLength + len]);
	}
	wchar[] buf;
	buf ~= textStore[start..gapStart];
	buf ~= textStore[gapEnd..gapEnd+ end - gapStart];
	return Utf.toString(buf);
}
/**
 * Removes the specified <code>TextChangeListener</code>.
 * <p>
 *
 * @param listener the listener
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
 * </ul>
 */
public void removeTextChangeListener(TextChangeListener listener){
	if (listener is null) error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
	for (int i=0; i<textListeners.getSize(); i++) {
		TypedListener typedListener = cast(TypedListener)textListeners.elementAt(i);
		if (typedListener.getEventListener () is listener) {
			textListeners.removeElementAt(i);
			break;
		}
	}
}
/**
 * Replaces the text with <code>newText</code> starting at position <code>start</code> 
 * for a length of <code>replaceLength</code>.  Notifies the appropriate listeners.
 * <p>
 *
 * When sending the TextChangingEvent, <code>newLineCount</code> is the number of 
 * lines that are going to be inserted and <code>replaceLineCount</code> is 
 * the number of lines that are going to be deleted, based on the change 
 * that occurs visually.  For example:
 * <ul>
 * <li>(replaceText,newText) ==> (replaceLineCount,newLineCount)
 * <li>("","\n") ==> (0,1)
 * <li>("\n\n","a") ==> (2,0)
 * </ul>
 * </p>
 *
 * @param start	start offset of text to replace
 * @param replaceLength start offset of text to replace
 * @param newText start offset of text to replace
 * 
 * @exception SWTException <ul>
 *   <li>ERROR_INVALID_ARGUMENT when the text change results in a multi byte
 *      line delimiter being split or partially deleted.  Splitting a line 
 *      delimiter by inserting text between the CR and LF characters of the 
 *      \r\n delimiter or deleting part of this line delimiter is not supported</li>
 * </ul>
 */
public void replaceTextRange(int start, int replaceLength, char[] newText){
	// check for invalid replace operations
	if (!isValidReplace(start, replaceLength, newText)) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);		

	// inform listeners
	StyledTextEvent event = new StyledTextEvent(this);
	event.type = StyledText.TextChanging;
	event.start = start;
	event.replaceLineCount = lineCount(start, replaceLength);
	event.text = newText;
	event.newLineCount = lineCount(newText);
	event.replaceCharCount = replaceLength;
	event.newCharCount = newText.length;
	sendTextEvent(event);

	// first delete the text to be replaced
	delete_renamed(start, replaceLength, event.replaceLineCount + 1);
	// then insert the new text
	insert(start, newText);
	// inform listeners
	event = new StyledTextEvent(this);
	event.type = StyledText.TextChanged;
	sendTextEvent(event);		
	// printLines();
}
/**
 * Sends the text listeners the TextChanged event.
 */
void sendTextEvent(StyledTextEvent event) {
	for (int i=0; i<textListeners.getSize(); i++) {
		(cast(StyledTextListener)textListeners.elementAt(i)).handleEvent(event);
	}
}		
/**
 * Sets the content to text and removes the gap since there are no sensible predictions 
 * about where the next change will occur.
 * <p>
 *
 * @param text the text
 */
public void setText (char[] text){
	textStore = Utf.toString16(text);
	gapStart = -1;
	gapEnd = -1;
	expandExp = 1;
	indexLines();
	StyledTextEvent event = new StyledTextEvent(this);
	event.type = StyledText.TextSet;
	event.text = "";
	sendTextEvent(event);
}
/**
 * Deletes text.
 * <p>
 * @param position the position at which the text to delete starts
 * @param length the length of the text to delete
 * @param numLines the number of lines that are being deleted
 */
void delete_renamed(int position, int length, int numLines) {
	if (length == 0) return;
		
	int startLine = getLineAtOffset(position);
	int startLineOffset = getOffsetAtLine(startLine);
	int endLine = getLineAtOffset(position + length);

	char[] endText = "";
	boolean splittingDelimiter = false;
	if (position + length < getCharCount()) {
		endText = getTextRange(position + length - 1, 2);
		if ((endText[0] == DWT.WCR) && (endText[1] == DWT.WLF)) {
			splittingDelimiter = true;
		    }
	}

	adjustGap(position + length, -length, startLine);
	RANGE[] oldLines = indexLines(position, length + (gapEnd - gapStart), numLines);
	
	// enlarge the gap - the gap can be enlarged either to the
	// right or left
	if (position + length == gapStart) {
		gapStart -= length;
	} else {
		gapEnd += length;
	}		

	// figure out the length of the new concatenated line, do so by
	// finding the first line delmiter after position
	int j = position;
	boolean eol = false;
	while (j < textStore.length && !eol) {
		if (j < gapStart || j >= gapEnd) {
			wchar ch = textStore[j];
			if (isDelimiter(ch)) {
				if (j + 1 < textStore.length) 
					if (ch == DWT.WCR && (textStore[j+1] == DWT.WLF))
						j++;
				eol = true;
			} 
		}
		j++;
	}	
	// update the line where the deletion started
	lines[startLine][1] = (position - startLineOffset) + (j - position);
	// figure out the number of lines that have been deleted
	int numOldLines = oldLines.length - 1;
	if (splittingDelimiter) numOldLines -= 1;
	// shift up the lines after the last deleted line, no need to update
	// the offset or length of the lines
	for (int i = endLine + 1; i < lineCount_var; i++) {
		lines[i - numOldLines]=lines[i];
	}
	lineCount_var -= numOldLines;
	gapLine = getLineAtPhysicalOffset(gapStart);		
}
}


class StyledTextEvent : Event {
	// used by LineStyleEvent
	StyleRange[] styles;		
	// used by LineBackgroundEvent
	Color lineBackground;
	// used by BidiSegmentEvent
	int[] segments;	
	// used by TextChangedEvent
	int replaceCharCount; 	
	int newCharCount; 
	int replaceLineCount;
	int newLineCount; 

this (StyledTextContent content) {
//	super();
	data = cast(Object)content;	
}
}


class StyledTextListener : TypedListener {
/**
 */
this(DWTEventListener listener) {
	super(listener);
}
/**
 * Process StyledText events by invoking the event's handler.
 */
public void handleEvent(Event e) {
	TextChangedEvent textChangedEvent;
	
	switch (e.type) {
		case StyledText.ExtendedModify:
			ExtendedModifyEvent extendedModifyEvent = new ExtendedModifyEvent(cast(StyledTextEvent) e);
			(cast(ExtendedModifyListener) eventListener).modifyText(extendedModifyEvent);
		break;
		
		case StyledText.LineGetBackground:
			LineBackgroundEvent lineBgEvent = new LineBackgroundEvent(cast(StyledTextEvent) e);
			(cast(LineBackgroundListener) eventListener).lineGetBackground(lineBgEvent);
			(cast(StyledTextEvent) e).lineBackground = lineBgEvent.lineBackground;
		break;
		
		case StyledText.LineGetSegments:
			BidiSegmentEvent segmentEvent = new BidiSegmentEvent(cast(StyledTextEvent) e);
			(cast(BidiSegmentListener) eventListener).lineGetSegments(segmentEvent);
			(cast(StyledTextEvent) e).segments = segmentEvent.segments;
		break;
		
		case StyledText.LineGetStyle:
			LineStyleEvent lineStyleEvent = new LineStyleEvent(cast(StyledTextEvent) e);
			(cast(LineStyleListener) eventListener).lineGetStyle(lineStyleEvent);
			(cast(StyledTextEvent) e).styles = lineStyleEvent.styles;
		break;

		case StyledText.VerifyKey:
			VerifyEvent verifyEvent = new VerifyEvent(e);
			(cast(VerifyKeyListener) eventListener).verifyKey(verifyEvent);
			e.doit = verifyEvent.doit;
		break;		

		case StyledText.TextChanged:
			textChangedEvent = new TextChangedEvent(cast(StyledTextContent) e.data);
			(cast(TextChangeListener) eventListener).textChanged(textChangedEvent);
		break;

		case StyledText.TextChanging:
			TextChangingEvent textChangingEvent = new TextChangingEvent(cast(StyledTextContent) e.data, cast(StyledTextEvent) e);
			(cast(TextChangeListener) eventListener).textChanging(textChangingEvent);
		break;

		case StyledText.TextSet:
			textChangedEvent = new TextChangedEvent(cast(StyledTextContent) e.data);
			(cast(TextChangeListener) eventListener).textSet(textChangedEvent);
		break;
		default : break;
	}
}
}


/**
 * Use StyledTextPrintOptions to specify printing options for the
 * StyledText.print(Printer, StyledTextPrintOptions) API.
 * <p>
 * The following example prints a right aligned page number in the footer,
 * sets the job name to "Example" and prints line background colors but no other
 * formatting:
 * </p>
 * <pre>
 * StyledTextPrintOptions options = new StyledTextPrintOptions();
 * options.footer = "\t\t&lt;page&gt;"; 
 * options.jobName = "Example";
 * options.printLineBackground = true;
 * 
 * Runnable runnable = styledText.print(new Printer(), options); 
 * runnable.run();
 * </pre>
 * @since 2.1
 */
public class StyledTextPrintOptions {
	/**
	 * Page number placeholder constant for use in <code>header</code>
	 * and <code>footer</code>. Value is <code>&lt;page&gt;</code>
	 */
	public static char[] PAGE_TAG;
	/**
	 * Separator constant for use in <code>header</code> and
	 * <code>footer</code>. Value is <code>\t</code>
	 */
	public static char[] SEPARATOR;
	/**
	 * Formatted text to print in the header of each page.
	 * <p>"left '\t' center '\t' right"</p>
	 * <p>left, center, right = &lt;page&gt; | #CDATA</p>
	 * <p>Header and footer are defined as three separate regions for arbitrary
	 * text or the page number placeholder &lt;page&gt;
	 * (<code>StyledTextPrintOptions.PAGE_TAG</code>). The three regions are 
	 * left aligned, centered and right aligned. They are separated by a tab
	 * character (<code>StyledTextPrintOptions.SEPARATOR</code>).
	 */
	public char[] header = null;
	/**
	 * Formatted text to print in the footer of each page.
	 * <p>"left '\t' center '\t' right"</p>
	 * <p>left, center, right = &lt;page&gt; | #CDATA</p>
	 * <p>Header and footer are defined as three separate regions for arbitrary
	 * text or the page number placeholder &lt;page&gt;
	 * (<code>StyledTextPrintOptions.PAGE_TAG</code>). The three regions are 
	 * left aligned, centered and right aligned. They are separated by a tab
	 * character (<code>StyledTextPrintOptions.SEPARATOR</code>).
	 */
	public char[] footer = null;
	/**
	 * Name of the print job.
	 */
	public char[] jobName = null;
	
	/**
	 * Print the text foreground color. Default value is <code>false</code>.
	 */
	public boolean printTextForeground = false;
	/**
	 * Print the text background color. Default value is <code>false</code>.
	 */
	public boolean printTextBackground = false;
	/**
	 * Print the font styles. Default value is <code>false</code>.
	 */
	public boolean printTextFontStyle = false;
	/**
	 * Print the line background color. Default value is <code>false</code>.
	 */
	public boolean printLineBackground = false;

	this(){
		PAGE_TAG = ("<page>");
		SEPARATOR = ("\t");
	}
}


/**
 * A StyledTextRenderer renders the content of a StyledText widget.
 * Subclasses can provide a different device (e.g., Display, Printer) 
 * to render on and implement abstract methods to return resources 
 * created on that device.
 */
 // TODO:
abstract class StyledTextRenderer {
	private Device device;					// device to render on
	protected Font regularFont, boldFont, italicFont, boldItalicFont;
	private int tabWidth;					// width in pixels of a tab character
	private int ascent, descent;
	private int lineEndSpaceWidth;			// width in pixels of the space used to represent line delimiters
	
/**
 * Creates an instance of <class>StyledTextRenderer</class>.
 * </p>
 * @param device Device to render on
 * @param regularFont Font to use for regular (non-bold) text
 * @param leftMargin margin to the left of the text
 */
this(Device device, Font regularFont) {
	this.device = device;
	this.regularFont = regularFont;
}
/**
 * Calculates the line height and space width.
 */
void calculateLineHeight() {
	GC gc = getGC();
	lineEndSpaceWidth = gc.stringExtent((" ")).x;	
	
	// don't assume that bold and normal fonts have the same height
	// fixes bug 41773
	Font originalFont = gc.getFont();
	FontMetrics metrics = gc.getFontMetrics();
	ascent = Math.max(ascent, metrics.getAscent() + metrics.getLeading());
	descent = Math.max(descent, metrics.getDescent());
	gc.setFont(getFont(DWT.BOLD));
	metrics = gc.getFontMetrics();
	ascent = Math.max(ascent, metrics.getAscent() + metrics.getLeading());
	descent = Math.max(descent, metrics.getDescent());
	gc.setFont(getFont(DWT.ITALIC));
	metrics = gc.getFontMetrics();
	ascent = Math.max(ascent, metrics.getAscent() + metrics.getLeading());
	descent = Math.max(descent, metrics.getDescent());
	gc.setFont(getFont(DWT.BOLD | DWT.ITALIC));
	metrics = gc.getFontMetrics();
	ascent = Math.max(ascent, metrics.getAscent() + metrics.getLeading());
	descent = Math.max(descent, metrics.getDescent());
	gc.setFont(originalFont);
	disposeGC(gc);
	
	// clear the font cache
	if (boldFont !is null) boldFont.dispose();
	if (italicFont !is null) italicFont.dispose();
	if (boldItalicFont !is null) boldItalicFont.dispose();
	boldFont = italicFont = boldItalicFont = null;
}
/**
 * Disposes the resource created by the receiver.
 */
void dispose() {
	if (boldFont !is null) boldFont.dispose();
	if (italicFont !is null) italicFont.dispose();
	if (boldItalicFont !is null) boldItalicFont.dispose();
	boldFont = italicFont = boldItalicFont = null;
}
/**
 * Dispose the specified GC.
 * Allows subclasses to reuse GCs.
 * </p>
 * @param gc GC to dispose.
 */
protected abstract void disposeGC(GC gc);
/** 
 * Draws a line of text at the specified location.
 * </p>
 *
 * @param line the line to draw
 * @param lineIndex	index of the line to draw
 * @param paintY y location to draw at
 * @param gc GC to draw on
 * @param widgetBackground the widget background color. 
 * 	Used as the default rendering color.
 * @param widgetForeground the widget foreground color. 
 * 	Used as the default rendering color. 
 * @param clearBackground true if the line background should be drawn
 * explicitly.
 */
void drawLine(char[] line, int lineIndex, int paintY, GC gc, Color widgetBackground, Color widgetForeground, boolean clearBackground) {
	int lineOffset = getContent().getOffsetAtLine(lineIndex);
	int lineLength = line.length;
	Point selection = getSelection();
	int selectionStart = selection.x;
	int selectionEnd = selection.y;
	int leftMargin = getLeftMargin();
	Color lineBackground = null;
	TextLayout layout = getTextLayout(line, lineOffset);
	Rectangle client = getClientArea();
	StyledTextEvent event = getLineBackgroundData(lineOffset, line);
	if (event !is null) {
		lineBackground = event.lineBackground;
	}
	if (lineBackground is null) {
		lineBackground = widgetBackground;
	}
	
	if (clearBackground &&
		(isFullLineSelection() == false || 
		 selectionStart > lineOffset || 
		 selectionEnd <= lineOffset + lineLength)) {
		// draw background if full selection is off or if line is not 
		// completely selected
		gc.setBackground(lineBackground);
		gc.setForeground(lineBackground);
		gc.fillRectangle(client.x + leftMargin, paintY, client.width, ascent + descent);
	}
	int paintX = client.x + leftMargin - getHorizontalPixel();
	if (selectionStart != selectionEnd) {
		Rectangle rect = layout.getLineBounds(0);
		drawLineBreakSelection(line, lineOffset, paintX + rect.x + rect.width, paintY, gc);
	}
	gc.setForeground(widgetForeground);
	gc.setBackground(lineBackground);
	
	if (selectionStart == selectionEnd || (selectionEnd <= lineOffset && selectionStart > lineOffset + lineLength - 1)) {
		layout.draw(gc, paintX, paintY);
	} else {
		int start = Math.max(0, selectionStart - lineOffset);
		int end = Math.min(lineLength, selectionEnd - lineOffset);
		layout.draw(gc, paintX, paintY, start, end - 1, getSelectionForeground(), getSelectionBackground());
	}
	disposeTextLayout(layout);
}
/** 
 * Draws the background of the line selection.
 * Implemented by subclasses for optional selection rendering.
 * </p>
 *
 * @param line the line to draw
 * @param lineOffset offset of the first character in the line.
 * 	Relative to the start of the document.
 * @param styles line styles
 * @param paintY y location to draw at
 * @param gc GC to draw on
 * @param bidi the bidi object to use for measuring and rendering 	text in bidi
 * locales. null when not in bidi mode.
 */
protected abstract void drawLineBreakSelection(char[] line, int lineOffset, int paintX, int paintY, GC gc);

/**
 * Returns the visible client area that can be used for rendering.
 * </p>
 * @return the visible client area that can be used for rendering.
 */
public abstract Rectangle getClientArea();
/**
 * Returns the <class>StyledTextContent</class> to use for line offset
 * calculations.
 * </p>
 * @return the <class>StyledTextContent</class> to use for line offset
 * calculations.
 */
protected abstract StyledTextContent getContent();
/**
 * Returns the Device that is being rendered on.
 * </p>
 * @return the Device that is being rendered on.
 */
Device getDevice() {
	return device;
}
int getBaseline() {
	return ascent;
}
/**
 * Returns the text segments that should be treated as if they 
 * had a different direction than the surrounding text.
 * </p>
 *
 * @param lineOffset offset of the first character in the line. 
 * 	0 based from the beginning of the document.
 * @param line text of the line to specify bidi segments for
 * @return text segments that should be treated as if they had a
 * 	different direction than the surrounding text. Only the start 
 * 	index of a segment is specified, relative to the start of the 
 * 	line. Always starts with 0 and ends with the line length. 
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the segment indices returned 
 * 		by the listener do not start with 0, are not in ascending order,
 * 		exceed the line length or have duplicates</li>
 * </ul>
 */
protected abstract int[] getBidiSegments(int lineOffset, char[] lineText);
/**
 *  Returns the Font according with the given style
 */
Font getFont(int style) {
	switch (style) {
		case DWT.BOLD:
			if (boldFont !is null) return boldFont;
			return boldFont = new Font(device, getFontData(style));
		case DWT.ITALIC:
			if (italicFont !is null) return italicFont;
			return italicFont = new Font(device, getFontData(style));
		case DWT.BOLD | DWT.ITALIC:
			if (boldItalicFont !is null) return boldItalicFont;
			return boldItalicFont = new Font(device, getFontData(style));
		default:
			return regularFont;
	}
	return regularFont;
}
FontData[] getFontData(int style) {
	FontData[] fontDatas = regularFont.getFontData();
	for (int i = 0; i < fontDatas.length; i++) {
		fontDatas[i].setStyle(style);
	}
	return fontDatas;
}
/**
 * Returns the GC to use for rendering and measuring.
 * Allows subclasses to reuse GCs.
 * </p>
 * @return the GC to use for rendering and measuring.
 */
protected abstract GC getGC();
/**
 * Returns the horizontal scroll position.
 * </p>
 * @return the horizontal scroll position.
 */
protected abstract int getHorizontalPixel();
public int getLeftMargin() {
	return 0;
}
/**
 * Returns the width in pixels of the space used to represent line delimiters.
 * @return the width in pixels of the space used to represent line delimiters.
 */
int getLineEndSpaceWidth() {
	return lineEndSpaceWidth;
}
/**
 * Returns the line background data for the given line or null if 
 * there is none. 
 * </p>
 * @param lineOffset offset of the line start relative to the start
 * 	of the content.
 * @param line line to get line background data for
 * @return line background data for the given line. may return null
 */
 // NOTE: change from protected to public
public abstract StyledTextEvent getLineBackgroundData(int lineOffset, char[] line);
/**
 * Returns the height in pixels of a line.
 * </p>
 * @return the height in pixels of a line.
 */
int getLineHeight() {
	return ascent + descent;
}
/**
 * Returns the line style data for the specified line.
 * The lineOffset and line may specify a segment of a logical line stored
 * in the <class>StyledTextContent</class> of the widget.
 * The returned styles are guaranteed to be at least partially on the
 * segment.
 * </p>
 * @param event the styles for the logical line
 * @param lineOffset offset of the line start relative to the start of 
 * 	the content.
 * @param line line to get line styles for
 * @return line style data for the given line segment. Styles may start 
 * 	before line start and end after line end but are guaranteed to be at 
 * 	least partially on the line.
 */
StyledTextEvent getLineStyleData(StyledTextEvent event, int lineOffset, char[] line) {
	int lineLength = line.length;
	
	if (event.styles !is null && getWordWrap()) {
		event.styles = getVisualLineStyleData(event.styles, lineOffset, lineLength);
	}
	return event;
}
/**
 * Returns the line style data for the given line or null if there is 
 * none. If there is a LineStyleListener but it does not set any styles, 
 * the StyledTextEvent.styles field will be initialized to an empty 
 * array.
 * </p>
 * 
 * @param lineOffset offset of the line start relative to the start of 
 * 	the content.
 * @param line line to get line styles for
 * @return line style data for the given line. Styles may start before 
 * 	line start and end after line end
 */
 //
public abstract StyledTextEvent getLineStyleData(int lineOffset, char[] line);
/**
 *
 */
protected abstract int getOrientation ();
/**
*
*/
public int getRightMargin() {
	return 0;
}
/**
 *
 */
protected abstract Color getSelectionForeground();
/**
 *
 */
protected abstract Color getSelectionBackground();
/**
 * Returns the widget selection.
 * Implemented by subclasses for optional selection rendering.
 * </p>
 * @return the widget selection.
 */
protected abstract Point getSelection();
/**
 * Returns styles for the specified visual (wrapped) line.
 * </p>
 * 
 * @param logicalStyles the styles for a logical (unwrapped) line
 * @param lineOffset offset of the visual line
 * @param lineLength length of the visual line
 * @return styles in the logicalStyles array that are at least 
 * 	partially on the specified visual line.
 */
StyleRange[] getVisualLineStyleData(StyleRange[] logicalStyles, int lineOffset, int lineLength) {
	int lineEnd = lineOffset + lineLength;
	int oldStyleCount = logicalStyles.length;
	int newStyleCount = 0;
	
	for (int i = 0; i < oldStyleCount; i++) {
		StyleRange style = logicalStyles[i];
		if (style.start < lineEnd && style.start + style.length > lineOffset) {
			newStyleCount++;
		}
	}
	if (newStyleCount != oldStyleCount) {
		StyleRange[] newStyles = new StyleRange[newStyleCount];
		for (int i = 0, j = 0; i < oldStyleCount; i++) {
			StyleRange style = logicalStyles[i];
			if (style.start < lineEnd && style.start + style.length > lineOffset) {
				newStyles[j++] = logicalStyles[i];						
			}
		}
		logicalStyles = newStyles;
	}
	return logicalStyles;
}
/**
 * Returns the word wrap state.
 * </p>
 * @return true=word wrap is on. false=no word wrap, lines may extend 
 * 	beyond the right side of the client area.
 */
protected abstract boolean getWordWrap();
/**
 * Returns whether the widget was created with the DWT.FULL_SELECTION style.
 * Implemented by subclasses for optional selection rendering.
 * </p>
 * @return true=the widget is running in full line selection mode, 
 * 	false=otherwise
 */
protected abstract boolean isFullLineSelection();
/**
 * Calculates the width in pixel of a tab character
 * </p>
 * @param tabLength number of space characters represented by a tab character.
 */
void setTabLength(int tabLength) {
	GC gc = getGC();
	char[] tabBuffer = "";
	
	for (int i = 0; i < tabLength; i++) {
		tabBuffer ~= " ";
	}
	tabWidth = gc.stringExtent(tabBuffer).x;
	disposeGC(gc);
}
/**
 *  Returns TextLayout given a line index and an array of styles 
 */
TextLayout getTextLayout(char[] line, int lineOffset) {
	TextLayout layout = createTextLayout(lineOffset);
	layout.setFont(regularFont);
	layout.setAscent(ascent);
	layout.setDescent(descent);
	layout.setText(line);
	layout.setOrientation(getOrientation());
	layout.setSegments(getBidiSegments(lineOffset, line));
	int[] intArr;
	intArr ~= tabWidth;
	layout.setTabs(intArr);
	int length = line.length;
	StyledTextEvent event = getLineStyleData(lineOffset, line);
	StyleRange[] styles = event !is null ? event.styles : null;
	int lastOffset = 0;
	if (styles !is null) {
		for (int styleIndex = 0; styleIndex < styles.length; styleIndex++) {
			StyleRange style = styles[styleIndex];
			if (style.isUnstyled()) continue;
			int start, end;
			if (lineOffset > style.start) {
				start = 0;
				end = Math.min (length, style.length - lineOffset + style.start);
			} else {
				start = style.start - lineOffset;
				end = Math.min(length, start + style.length);
			}
			if (start >= length) break;
			if (lastOffset != start) {
				layout.setStyle(null, lastOffset, start - 1);	
			}
			TextStyle textStyle = new TextStyle(getFont(style.fontStyle), style.foreground, style.background);
			layout.setStyle(textStyle, start, end - 1);
			lastOffset = end;
		}
	}
	if (lastOffset != length) layout.setStyle(null, lastOffset, length);
	return layout;
}
TextLayout createTextLayout(int lineOffset) {
	return new TextLayout(device);
}
void disposeTextLayout (TextLayout layout) {
	layout.dispose();
}
}


public class StyleRange {
	
	public int start;		// style start offset. 0 based from the document start
	public int length;		// style length.	
	public Color foreground; 
	public Color background;
	public int fontStyle = DWT.NORMAL;	// may be DWT.NORMAL or DWT.BOLD

public this() {
}
/** 
 * Create a new style range.
 * <p>
 *
 * @param start start offset of the style
 * @param length length of the style 
 * @param foreground foreground color of the style, null if none 
 * @param background background color of the style, null if none
 */
public this(int start, int length, Color foreground, Color background) {
	this.start = start;
	this.length = length;
	this.foreground = foreground;
	this.background = background;
}

/** 
 * Create a new style range.
 * <p>
 *
 * @param start start offset of the style
 * @param length length of the style 
 * @param foreground foreground color of the style, null if none 
 * @param background background color of the style, null if none
 * @param fontStyle font style of the style, may be DWT.NORMAL or DWT.BOLD
 */
public this(int start, int length, Color foreground, Color background, int fontStyle) {
	this.start = start;
	this.length = length;
	this.foreground = foreground;
	this.background = background;
	this.fontStyle = fontStyle;
}

/**
 * Compare the specified object to this StyleRange and answer if the two 
 * are equal. The object must be an instance of StyleRange and have the
 * same field values.
 * <p>
 *
 * @param object the object to compare with this object
 * @return true if the objects are equal, false otherwise
 */
public boolean equals(Object object) {
	StyleRange style;
	if (object is this) return true;
	if (cast(StyleRange)object) style = cast(StyleRange)object;
	else return false;
	if (this.start != style.start) return false;
	if (this.length != style.length) return false;
	if (this.foreground !is null) {
		if (!this.foreground.equals(style.foreground)) return false;
	} else if (style.foreground !is null) return false;
	if (this.background !is null) {
		if (!this.background.equals(style.background)) return false;
	} else if (style.background !is null) return false; 
	if (this.fontStyle != style.fontStyle) return false;
	return true;
}
/**
 * Returns an integer hash code for the receiver. Objects which are
 * equal answer the same value for this method.
 * <p>
 *
 * @return the receiver's hash
 */
public int hashCode() {
	int code = start + length;
	
	if (foreground !is null)
		code += foreground.hashCode();
	if (background !is null)
		code += background.hashCode();
	return code + fontStyle;
}
/**
 * Returns whether or not the receiver is unstyled (i.e., does not have any 
 * style attributes specified).
 * <p>
 *
 * @return true if the receiver is unstyled, false otherwise.
 */
public boolean isUnstyled() {
	if (this.foreground !is null) return false;
	if (this.background !is null) return false;
	if (this.fontStyle != DWT.NORMAL) return false;
	return true;
}
/**
 * Compares the specified object to this StyleRange and answer if the two 
 * are similar. The object must be an instance of StyleRange and have the
 * same field values for except for start and length.
 * <p>
 *
 * @param object the object to compare with this object
 * @return true if the objects are similar, false otherwise
 */
public boolean similarTo(StyleRange style) {
	if (this.foreground !is null) {
		if (!this.foreground.equals(style.foreground)) return false;
	} else if (style.foreground !is null) return false;
	if (this.background !is null) {
		if (!this.background.equals(style.background)) return false;
	} else if (style.background !is null) return false; 
	if (this.fontStyle != style.fontStyle) return false;
	return true;
}
/**
 * Answers a new StyleRange with the same values as this StyleRange.
 * <p>
 *
 * @return a shallow copy of this StyleRange
 */	
public Object clone() {
 	StyleRange style = new StyleRange(start, length, foreground, background, fontStyle);
	return style;
}
/** 
 * Answers a string description of the receiver.
 * <p>
 *
 * @return a printable representation for the receiver.
 */
public char[] toString() {
	
	char[] buf = Int.toString(start) ~ "," ~ Int.toString(length) ~ " fg:" ~ foreground.toString ~  " bg:"  ~ background.toString ~ " fStyle:";
	
	if (fontStyle == DWT.NORMAL) {
		buf ~= ("normal");
	} else if (fontStyle == DWT.BOLD) {
		buf ~= ("bold");
	}
	return buf;
}
}



/**
 * This event is sent by the StyledTextContent implementor when a change to 
 * the text occurs.
 */
public class TextChangedEvent : TypedEvent {
/**
 * Create the TextChangedEvent to be used by the StyledTextContent implementor.
 * <p>
 *
 * @param source the object that will be sending the TextChangedEvent, 
 * 	cannot be null	
 */
public this(StyledTextContent source) {
	super(cast(Object)source);
}
}


/**
 * The StyledText widget implements this listener to receive
 * notifications when changes to the model occur.
 * It is not intended to be implemented by clients or by 
 * implementors of StyledTextContent. 
 * Clients should listen to the ModifyEvent or ExtendedModifyEvent 
 * that is sent by the StyledText widget to receive text change 
 * notifications.
 * Implementors of StyledTextContent should call the textChanging
 * and textChanged methods when text changes occur as described 
 * below. If the entire text is replaced the textSet method 
 * should be called instead.
 */
public interface TextChangeListener : DWTEventListener {

/**
 * This method is called when the content is about to be changed.
 * Callers also need to call the textChanged method after the 
 * content change has been applied. The widget only updates the 
 * screen properly when it receives both events. 
 * 
 * @param event the text changing event. All event fields need
 * 	to be set by the sender.
 * @see TextChangingEvent
 */
public void textChanging(TextChangingEvent event);
/**
 * This method is called when the content has changed.
 * Callers need to have called the textChanging method prior to 
 * applying the content change and calling this method. The widget 
 * only updates the screen properly when it receives both events.
 * 
 * @param event the text changed event
 */
public void textChanged(TextChangedEvent event);
/**
 * This method is called instead of the textChanging/textChanged 
 * combination when the entire old content has been replaced 
 * (e.g., by a call to StyledTextContent.setText()).
 * 
 * @param event the text changed event
 */
public void textSet(TextChangedEvent event);
}


/**
 * This event is sent by the StyledTextContent implementor when a change
 * to the text is about to occur.
 */
public class TextChangingEvent : TypedEvent {
	/**
	 * Start offset of the text that is going to be replaced
	 */
	public int start;
	/**
	 * Text that is going to be inserted or empty string
	 * if no text will be inserted
	 */
	public char[] newText;
	/**
	 * Length of text that is going to be replaced
	 */
	public int replaceCharCount;
	/**
	 * Length of text that is going to be inserted
	 */
	public int newCharCount;
	/**
	 * Number of lines that are going to be replaced
	 */
	public int replaceLineCount;
	/**
	 * Number of new lines that are going to be inserted
	 */
	public int newLineCount;

/**
 * Create the TextChangedEvent to be used by the StyledTextContent implementor.
 * <p>
 *
 * @param source the object that will be sending the new TextChangingEvent, 
 * 	cannot be null	
 */
public this(StyledTextContent source) {
	super(cast(Object)source);
}
this(StyledTextContent source, StyledTextEvent e) {
	super(cast(Object)source);
	start = e.start;
	replaceCharCount = e.replaceCharCount;
	newCharCount = e.newCharCount;
	replaceLineCount = e.replaceLineCount;
	newLineCount = e.newLineCount;
	newText = e.text;
}

}


/**
 * An instance of class <code>WrappedContent</code> is used by 
 * StyledText to display wrapped lines. Lines are wrapped at word 
 * breaks which are marked by a space character. Trailing space 
 * behind words is kept on the current line.
 * If the last remaining word on a line can not be fully displayed 
 * the line is wrapped character by character.
 * WrappedContent wraps a StyledTextContent which provides the line
 * data. The start offset and length of wrapped lines is calculated
 * and updated based on recalculation requests and text changes.
 * <p>
 * All public methods in this class implement the 
 * <code>StyledTextContent</code> interface. Package visible 
 * methods are internal API for use by <code>StyledText</code>.
 * </p>
 */
class WrappedContent : StyledTextContent {

	
	
    static int LINE_OFFSET = 0; 	// index of line offset in visualLines array
    static int LINE_LENGTH = 1; 	// index of line lenght in visualLines array
	    
    StyledTextRenderer renderer;
	StyledTextContent logicalContent;
	RANGE[] visualLines; 				 // start and length of each visual line
	int visualLineCount = 0;

	
/**
 * Create a new instance.
 * 
 * @param renderer <class>StyledTextRenderer</class> that renders 
 * 	the lines wrapped by the new instance.
 * @param logicalContent StyledTextContent that provides the line 
 * 	data.
 */
this(StyledTextRenderer renderer, StyledTextContent logicalContent) {
    this.renderer = renderer;
    this.logicalContent = logicalContent;
}
/**
 * @see StyledTextContent#addTextChangeListener(TextChangeListener)
 */
public void addTextChangeListener(TextChangeListener listener) {
    logicalContent.addTextChangeListener(listener);
}
/**
 * Grow the lines array to at least the specified size.
 * <p>
 * 
 * @param numLines number of elements that the array should have
 * 	at a minimum
 */
private void ensureSize(int numLines) {
	int size = visualLines.length;
	if (size >= numLines) {
		return;
	}
	RANGE[] newLines = new RANGE[Math.max(size * 2, numLines)];
	SimpleType!(RANGE).arraycopy(visualLines, 0, newLines, 0, size);
	visualLines = newLines;
	resetVisualLines(size, visualLines.length - size);	
}
/**
 * @see StyledTextContent#getCharCount()
 */
public int getCharCount() {
    return logicalContent.getCharCount();
}
/**
 * @return the visual (wrapped) line at the specified index
 * @see StyledTextContent#getLine(int)
 */
public char[] getLine(int lineIndex) {
	char[] line;
	
	// redirect call to logical content if there are no wrapped lines
	if (visualLineCount == 0) {
		line = logicalContent.getLine(lineIndex);
	}
	else {
		if (lineIndex >= visualLineCount || lineIndex < 0) {
			DWT.error (__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);
		}		
		line = logicalContent.getTextRange(visualLines[lineIndex][LINE_OFFSET], visualLines[lineIndex][LINE_LENGTH]);
	}
    return line;
}
/**
 * Returns the visual (wrapped) line at given offset.
 * <p>
 * The offset is ambiguous if it identifies the end of a visual line and 
 * there is another visual line below. In this case the end of the visual
 * line has the same offset as the beginning of the next visual line 
 * since the visual line break is not represented by any character in the
 * logical line.
 * In this ambiguous case the offset is assumed to represent the end of a
 * visual line and the index of the first visual line is returned.
 * </p>
 * 
 * @param offset offset of the desired line. 
 * @return the index of the visual (wrapped) line at the specified offset
 * @see StyledTextContent#getLineAtOffset(int)
 */
public int getLineAtOffset(int offset) {
	int lastLine = visualLineCount - 1;
	int lastChar;

	// redirect call to logical content if there are no wrapped lines
	if (visualLineCount == 0) {
		return logicalContent.getLineAtOffset(offset);
	}
	// can't use getCharCount to get the number of characters since this
	// method is called in textChanged, when the logicalContent used by
	// getCharCount has already changed. at that point the visual lines
	// have not been updated yet and we thus need to use the old character
	// count which is only available in the visual content.
	lastChar = visualLines[lastLine][LINE_OFFSET] + visualLines[lastLine][LINE_LENGTH];
	if (offset < 0 || (offset > 0 && offset > lastChar)) {
	    DWT.error (__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);
	}
	// if last line and the line is not empty you can ask for 
	// a position that doesn't exist (the one to the right of the 
	// last character) - for inserting
	if (offset == lastChar) {
		return lastLine;
	}

	int high = visualLineCount;
	int low = -1;
	int index = visualLineCount;
	while (high - low > 1) {
		index = (high + low) / 2;
		int lineStart = visualLines[index][LINE_OFFSET];
		if (offset >= lineStart) {
			int lineEnd = lineStart + visualLines[index][LINE_LENGTH];
		    low = index;			
		    if (offset <= lineEnd) {
		    	break;
		    }
		} 
		else {
			high = index;
		}
	}
	if (low > 0 && offset == visualLines[low - 1][LINE_OFFSET] + visualLines[low - 1][LINE_LENGTH]) {
		// end of a visual line/beginning of next visual line is ambiguous 
		// (they have the same offset). always return the first visual line
		low--;
	}
	return low;
}
/**
 * @return the number of visual (wrapped) lines
 * @see StyledTextContent#getLineCount()
 */
public int getLineCount() {
	int lineCount = visualLineCount;
	
	// redirect call to logical content if there are no wrapped lines
	if (visualLineCount == 0) {
		lineCount = logicalContent.getLineCount();
	}
    return lineCount;
}
/**
 * @see StyledTextContent#getLineDelimiter()
 */
public char[] getLineDelimiter() {
    return logicalContent.getLineDelimiter();
}
/**
 * @return the start offset of the visual (wrapped) line at the given 
 * 	index
 * @see StyledTextContent#getOffsetAtLine(int)
 */
public int getOffsetAtLine(int lineIndex) {
	int offset;
	
	// redirect call to logical content if there are no wrapped lines
	if (visualLineCount == 0) {
		offset = logicalContent.getOffsetAtLine(lineIndex);
	}
	else {
		if (lineIndex >= visualLineCount || lineIndex < 0) {
			DWT.error (__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);
		}
		offset = visualLines[lineIndex][LINE_OFFSET];
	}
    return offset;
}
/**
 * @see StyledTextContent#getTextRange(int, int)
 */
public char[] getTextRange(int start, int length) {
    return logicalContent.getTextRange(start, length);
}
/**
 * Returns the number of visual (wrapped) lines.
 * 
 * @return the number of visual (wrapped) lines
 */
int getVisualLineCount() {
	return visualLineCount;	
}
/**
 * @see StyledTextContent#removeTextChangeListener(TextChangeListener)
 */
public void removeTextChangeListener(TextChangeListener listener) {
    logicalContent.removeTextChangeListener(listener);
}
/**
 * Reset the visual (wrapped) lines in the specified range.
 * If the range specifies partial logical lines (e.g., startLine is
 * the second of two visual lines) it is extended to reset all visual 
 * lines of a logical line.
 * Following the reset the logical lines in the reset visual range are 
 * rewrapped.
 * <p>
 * 
 * @param startLine index of the first visual line 
 * @param lineCount number of visual lines
 */
void reset(int startLine, int lineCount) {
    if (lineCount <= 0 || visualLineCount == 0) {
        return;
	}       
	reset(startLine, lineCount, true);
}
/**
 * Reset the visual (wrapped) lines in the specified range.
 * If the range specifies partial logical lines (e.g., startLine is
 * the second of two visual lines) it is extended to reset all visual 
 * lines of a logical line.
 * <p>
 * 
 * @param startLine index of the first visual line 
 * @param lineCount number of visual lines
 * @param wrap true=rewrap the logical lines in the reset visual range 
 * 	false=don't rewrap lines. Visual lines will be left in an inconsistent
 * 	state since there will be a range of unwrapped and unknown lines.
 * @return the first visual line that was reset
 */
private int reset(int startLine, int lineCount, boolean wrap) {
    if (lineCount <= 0) {
        return startLine;
	}       
    // make sure that all visual lines of the first logical line are 
    // being reset. visualFirstLine is the first visual line of the 
    // first logical line that has at least one visual line reset.
	int visualFirstLineOffset = getOffsetAtLine(startLine);
    int logicalFirstLine = logicalContent.getLineAtOffset(visualFirstLineOffset);
    int logicalFirstLineOffset = logicalContent.getOffsetAtLine(logicalFirstLine);
    int visualFirstLine = getLineAtOffset(logicalFirstLineOffset);

    lineCount += startLine - visualFirstLine;
    startLine = visualFirstLine;
        
    // make sure that all visual lines of the last logical line are 
    // being reset.
	int lastLine = startLine + lineCount - 1;
    int lastLineEnd = visualLines[lastLine][LINE_OFFSET] + visualLines[lastLine][LINE_LENGTH];
    int logicalEndLine = 0;
        
    while (lastLine < visualLineCount - 1 && lastLineEnd == visualLines[lastLine + 1][LINE_OFFSET]) {
    	lastLine++;
    	lastLineEnd = visualLines[lastLine][LINE_OFFSET] + visualLines[lastLine][LINE_LENGTH];
    }
    if (wrap) {
		if (lastLine == visualLineCount - 1) {
			logicalEndLine = logicalContent.getLineCount();
		}
		else {
			logicalEndLine = logicalContent.getLineAtOffset(visualLines[lastLine + 1][LINE_OFFSET]);
		}
    }
    lineCount = lastLine - startLine + 1;
	resetVisualLines(startLine, lineCount);
	visualLineCount -= lineCount;
	if (wrap) {
	    // always recalculate line wrap immediately after a reset 
	    // because the content always needs to be in a usable state.
	    // i.e., there must not be any reset but unwrapped lines
	    wrapLineRange(logicalFirstLine, logicalEndLine, startLine);
	}
	return startLine;
}
/**
 * Reset the visual (wrapped) lines in the specified range.
 * <p>
 * 
 * @param startLine index of the first visual line 
 * @param lineCount number of visual lines
 */
private void resetVisualLines(int startLine, int lineCount) {
    int endLine = startLine + lineCount;

	for (int i = startLine; i < endLine; i++) {
//		int[] intArr;
		RANGE intArr;
		intArr[0] = -1;
		intArr[1] = -1;
		visualLines[i] = intArr;		
	}
}
/**
 * @see StyledTextContent#replaceTextRange(int, int, String)
 */
public void replaceTextRange(int start, int replaceLength, char[] text) {
	logicalContent.replaceTextRange(start, replaceLength, text);
}
/**
 * @see StyledTextContent#setText(String)
 */
public void setText(char[] text) {
    logicalContent.setText(text);
}
/**
 * Set the line wrap data for the specified visual (wrapped) line.
 * <p>
 * 
 * @param visualLineIndex index of the visual line
 * @param visualLineOffset start offset of the visual line, relative 
 * 	to the start of the document
 * @param visualLineLength length of the visual line
 */
private void setVisualLine(int visualLineIndex, int visualLineOffset, int visualLineLength) {
	ensureSize(visualLineCount + 1);
	// is the space for the visual line already taken? can happen if 
	// there are more visual lines for a given logical line than before
	if (visualLines[visualLineIndex][LINE_OFFSET] != -1) {
		SimpleType!(RANGE).arraycopy(visualLines, visualLineIndex, visualLines, visualLineIndex + 1, visualLineCount - visualLineIndex);

		RANGE range;
		visualLines[visualLineIndex] = range;
	}
	visualLines[visualLineIndex][LINE_OFFSET] = visualLineOffset;
	visualLines[visualLineIndex][LINE_LENGTH] = visualLineLength;
	visualLineCount++;
}
/** 
 * Recalculates the line wrap for the lines affected by the 
 * text change.
 * <p>
 *
 * @param startOffset	the start offset of the text change
 * @param newLineCount the number of inserted lines
 * @param replaceLineCount the number of deleted lines
 * @param newCharCount the number of new characters
 * @param replaceCharCount the number of deleted characters
 */  
void textChanged(int startOffset, int newLineCount, int replaceLineCount, int newCharCount, int replaceCharCount) {
	// do nothing if there are no wrapped lines
	if (visualLineCount == 0) {
		return;
	}
	int logicalStartLine = logicalContent.getLineAtOffset(startOffset);
	int visualStartLine = getLineAtOffset(startOffset);
	int visualReplaceLastLine = visualLineCount - 1;
	int textChangeDelta = newCharCount - replaceCharCount;
		
	if (replaceLineCount > 0) {	
		visualReplaceLastLine = getLineAtOffset(startOffset + replaceCharCount);
	    // at the start of a visual line/end of the previous visual line?
		if ((visualReplaceLastLine == 0 || 
		    visualLines[visualReplaceLastLine][LINE_OFFSET] == visualLines[visualReplaceLastLine - 1][LINE_OFFSET] + visualLines[visualReplaceLastLine - 1][LINE_LENGTH]) &&
		    visualReplaceLastLine != visualLineCount - 1) {
			visualReplaceLastLine++;
		}		
		visualStartLine = reset(visualStartLine, visualReplaceLastLine - visualStartLine + 1, false);
	}
	else {
		visualStartLine = reset(visualStartLine, 1, false);
	}
	visualReplaceLastLine = wrapLineRange(logicalStartLine, logicalStartLine + 1 + newLineCount, visualStartLine);
	for (int i = visualReplaceLastLine; i < visualLineCount; i++) {
//		visualLines[i][LINE_OFFSET] += textChangeDelta;
		visualLines[i][LINE_OFFSET] = textChangeDelta + visualLines[i][LINE_OFFSET];
	}
}
/**
 * Wrap the logical lines in the given range at the current client 
 * area width of the StyledText widget
 * <p>
 * 
 * @param startLine first logical line to wrap
 * @param endLine line after last logical line 
 * @param visualLineIndex visual (wrapped) line index that startLine
 * 	corresponds to.
 * @return index of the line following the last wrapped line
 */
private int wrapLineRange(int startLine, int endLine, int visualLineIndex) {
	int emptyLineCount = 0;
		
	int width = renderer.getClientArea().width - renderer.getLeftMargin() - renderer.getRightMargin();
	visualLineIndex = wrapLineRange(startLine, endLine, visualLineIndex, width);
	// is there space left for more visual lines? can happen if there are fewer
	// visual lines for a given logical line than before
	for (int i = visualLineIndex; i < visualLines.length; i++, emptyLineCount++) {
	    if (visualLines[i][LINE_OFFSET] != -1) {
	        break;
	    }
	}
	if (emptyLineCount > 0) {
		int copyLineCount = visualLineCount - visualLineIndex;
		SimpleType!(RANGE).arraycopy(visualLines, visualLineIndex + emptyLineCount, visualLines, visualLineIndex, copyLineCount);
		resetVisualLines(visualLineIndex + copyLineCount, emptyLineCount);
	}
	return visualLineIndex;
}
/**
 * Wrap the lines in the given range. Skip lines that have already 
 * been wrapped.
 * <p>
 * 
 * @param startLine first logical line to wrap
 * @param endLine line after last logical line 
 * @param visualLineIndex visual (wrapped) line index that startLine
 * 	corresponds to.
 * @param width line width to wrap at
 * @return index of last wrapped line
 */
private int wrapLineRange(int startLine, int endLine, int visualLineIndex, int width) {
	// if there are no wrapped lines and the width is 0 the widget has
	// not been made visible/sized yet. don't wrap until the widget size 
	// is known.
	if (visualLineCount == 0 && width == 0) {
		return visualLineIndex;
	}

	for (int i = startLine; i < endLine; i++) {
	    char[] line = logicalContent.getLine(i);
   	    int lineOffset = logicalContent.getOffsetAtLine(i);
   	    int lineLength = line.length;
   		if (lineLength == 0) {
			setVisualLine(visualLineIndex, lineOffset, 0);
			visualLineIndex++;
			continue;
   		}
		TextLayout layout = renderer.getTextLayout(line, lineOffset);
		layout.setWidth(Math.max(1, width));
		int[] offsets = layout.getLineOffsets();
		for (int j = 0; j < offsets.length - 1; j++) {
			setVisualLine(visualLineIndex++, lineOffset + offsets[j], offsets[j+1] - offsets[j]);
		}
		renderer.disposeTextLayout(layout); 
	}
	return visualLineIndex;
}
/**
 * Wrap all logical lines at the current client area width of the 
 * StyledText widget
 */
void wrapLines() {
	int width = renderer.getClientArea().width - renderer.getLeftMargin() - renderer.getRightMargin();
	wrapLines(width);
}
/**
 * Wrap all logical lines at the given width.
 * <p>
 * 
 * @param width width to wrap lines at
 */
void wrapLines(int width) {
    int lineCount = logicalContent.getLineCount();

	visualLineCount = 0;
	visualLines = new RANGE[lineCount];
	resetVisualLines(0, visualLines.length);		
	wrapLineRange(0, lineCount, 0, width);	
}
}
